且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

从线程内分叉是否安全?

更新时间:2021-10-17 21:52:57

问题在于 fork() 只复制调用线程,子线程中持有的任何互斥锁将永远锁定在分叉子线程中.pthread 解决方案是 pthread_atfork() 处理程序.这个想法是你可以注册 3 个处理程序:一个 prefork、一个父处理程序和一个子处理程序.当 fork() 发生时,在 fork 之前调用 prefork 并期望获得所有应用程序互斥锁.父进程和子进程都必须分别释放父进程和子进程中的所有互斥锁.

The problem is that fork() only copies the calling thread, and any mutexes held in child threads will be forever locked in the forked child. The pthread solution was the pthread_atfork() handlers. The idea was you can register 3 handlers: one prefork, one parent handler, and one child handler. When fork() happens prefork is called prior to fork and is expected to obtain all application mutexes. Both parent and child must release all mutexes in parent and child processes respectively.

这还不是故事的结局!库调用 pthread_atfork 来为库特定的互斥体注册处理程序,例如 Libc 就是这样做的.这是一件好事:应用程序不可能知道第 3 方库持有的互斥锁,因此每个库都必须调用 pthread_atfork 以确保在发生 时清除它自己的互斥锁>fork().

This isn't the end of the story though! Libraries call pthread_atfork to register handlers for library specific mutexes, for example Libc does this. This is a good thing: the application can't possibly know about the mutexes held by 3rd party libraries, so each library must call pthread_atfork to ensure it's own mutexes are cleaned up in the event of a fork().

问题在于,pthread_atfork 处理程序为不相关的库调用的顺序是未定义的(这取决于程序加载库的顺序).因此,这意味着从技术上讲,由于竞争条件,可能会在 prefork 处理程序内部发生死锁.

The problem is that the order that pthread_atfork handlers are called for unrelated libraries is undefined (it depends on the order that the libraries are loaded by the program). So this means that technically a deadlock can happen inside of a prefork handler because of a race condition.

例如,考虑这个序列:

  1. 线程 T1 调用 fork()
  2. 在 T1 中调用 libc prefork 处理程序(例如,T1 现在持有所有 libc 锁)
  3. 接下来,在线程 T2 中,第 3 方库 A 获取自己的互斥锁 AM,然后进行需要互斥锁的 libc 调用.这会阻塞,因为 libc 互斥锁由 T1 持有.
  4. 线程 T1 为库 A 运行 prefork 处理程序,该处理程序阻塞等待获取由 T2 持有的 AM.

这是您的死锁,它与您自己的互斥锁或代码无关.

There's your deadlock and its unrelated to your own mutexes or code.

这实际上发生在我曾经参与过的一个项目中.我当时找到的建议是选择 fork 或 thread,但不能两者兼而有之.但对于一些可能不实用的应用程序.

This actually happened on a project I once worked on. The advice I had found at that time was to choose fork or threads but not both. But for some applications that's probably not practical.