诡异的死锁
事情是这样的,观察到某台机器上出现了卡死的现象,即没有刷新日志,cpu 使用也较低,怀疑是不是出现了死锁。
由于程序采用的是 master + worker
的模式,首先 gdb attach 观察 master 情况,发现 master 执行正常,没有 lock wait 相关的堆栈;然后 gdb attach 观察 worker 情况,结果发现 worker 堆栈上有 lock wait 的情况,果然是出现了死锁,但 worker 上的其他线程并没有发现在等待锁的情况。
根据堆栈,找到 worker 的代码,重新梳理了一下代码,检查了 std::mutex 相关的函数调用,并没有出现嵌套调用的情况,也没有出现递归调用的情况,和上面发现 worker 其他线程没有等待锁的情况相吻合。
说明 worker 的死锁,并非由于 worker 内部的多线程造成的。那么就很诡异了,不是 worker 内部死锁,难道是多进程死锁?
排查验证
重新又检查了 worker 各个线程的堆栈情况,发现确实只有一个线程出现 lock wait 相关的堆栈; 并且又检查了一下 master 进程内部的各个线程,堆栈也都正常。
那 worker 锁住的这个线程,到底是因为什么原因?梳理 worker 代码,找到 std::mutex 相关的函数调用,发现 master 调用的一个函数使用到了 std::mutex,但是该函数内部逻辑也较为简单,不会一直占用这把锁。
没有头绪,谷歌搜索了一些类似的问题,找到了一点端倪。主进程 fork 之后,仅会复制发起调用的线程,不会复制其他线程,如果某个线程占用了某个锁,但是到了子进程,该线程是蒸发掉的,子进程会拷贝这把锁,但是不知道谁能释放,最终死锁。
确实符合这个程序的行为,并且确实是多进程下子进程的死锁,而且找不到其他线程也在等待锁。
接下来,写一个 demo 验证一下,是否 fork 不会复制子线程,并且有可能造成死锁。
fork demo 验证
简单写一个 demo: