fork中的死锁问题
背景
当我们通过fork去创建子进程时,当父/子进程都涉及到锁的操作,可能会产生死锁。
代码样例
#include <iostream>
#include <mutex>
#include <unistd.h>
std::mutex m;
int main() {std::cout << "main process begin" << std::endl;m.lock();int pid = fork();if (pid == -1) {std::cout << "fork failed" << std::endl;return -1;}if(pid == 0){ // 子进程m.lock();std::cout << "child process run" << std::endl;} else {}m.unlock();while (true) {}return 0;
}
代码示例中,父进程持有锁m,然后通过fork进行进程的创建,这个时候子进程里也进行锁操作,这个时候子进程就会死锁在这里
根因
当我们通过fork创建子进程时,进程会继承父进程的内存空间(写时复制技术,copy-on-write),包括代码段,堆栈,堆和数据段。
在子进程中锁定m时,这个时候从父进程里继承的m的锁状态处于锁定状态,这是再去m.lock,那就会死锁。
一些解决方法
如果子进程能够访问到锁,那锁定前先解锁
if(pid == 0){ // 子进程m.unlock(); // 锁定前,先解锁m.lock();std::cout << "child process run" << std::endl;} else {}
如果子进程不方便访问到锁,使用 pthread_atfork()
std::mutex m;
void child() {m.unlock();
}
int main() {pthread_atfork(nullptr, nullptr, child); // 三个参数分别时,prepare,parent,child
}
- prepare 处理器在 fork() 调用之前执行,通常用于获取那些需要在 fork() 期间保持的锁。
- parent 处理器在 fork() 调用之后,在父进程中执行,通常用于释放 prepare 处理器中获取的锁。
- child 处理器在 fork() 调用之后,在子进程中执行,也通常用于释放 prepare 处理器中获取的锁。
总结
- 我们要尽量在多线程程序中使用fork()
- 使用fork()后立即调用exec()
- 避免在持有锁时调用fork()
当然当我们编写多进程大型程序时,很难避免,特别是引用了一些三方库这些不受控的代码