混合使用线程、fork和mutex时,我需要注意什么?

8
如果我在一个线程持有互斥锁的进程中进行fork,那么如果我立即在子进程中执行exec,我是否相对安全?在exec之前,哪些操作是安全的?
如果执行fork的线程然后在调用exec之前释放互斥锁,这会导致问题吗?如果我尝试在父进程在fork之前拥有(可能仍然拥有)的子进程中获取互斥锁会发生什么?
答案在不同平台上是否不同?我主要关心Unix变体,特别是Linux。但我也很好奇NT。当然,NT(据我所知)没有fork
2个回答

10
请查看 pthread_atfork,特别是 RATIONALE 部分,讨论了在多线程环境中与 fork 相关的问题。它还提示了进程的子进程和父进程在 fork 发生前后应该是有效的。

更新:RATIONALE 部分是非规范的,并且与标准的其他部分存在冲突。请参见 Dave Butenhof 的 此缺陷报告 以获取更多详细信息。

fork 后立即执行 exec 对于任何多线程程序状态(即持有任何互斥量的线程)都应该是安全的。在 forkexec 之间可能发生的事情如下:

最重要的是,在子进程中只复制了一个线程(调用 fork 的线程)。因此,任何在调用 fork 时由 另一个 线程持有的互斥量都会永久锁定。也就是说(假设未设置进程共享的互斥量),其在子进程中的 副本 会被永久锁定,因为没有线程来解锁它。

如果可能的话,在 fork 后释放互斥量是安全的,即,如果调用 fork 的线程在第一次拥有该互斥量。这通常是 pthread_atfork 处理程序的工作方式:在 fork 之前锁定互斥量,在子进程中解锁并在父进程中解锁。

关于获取进程在 fork 前所拥有的互斥量(记住,我们讨论的是子进程地址空间中的副本):如果是由 fork 调用线程 拥有 的,则适用于递归锁定(适用于 PTHREAD_MUTEX_RECURSIVE);如果由另一个线程拥有,则它将永久锁定,并且无法重新获取。

通过注册适当的 pthread_atfork 处理程序,第三方库可以提供在 forkexec 之间安全使用的保证。(我希望这主要来自编程语言运行时,而不是通用库)。


经过更多的研究,我建议完全避免依赖于 pthread_atfork,并且在 fork 和 exec 之间仅进行异步信号安全调用(放弃 fork/exec 并使用 posix_spawn 将会更好)。
问题在于,fork 本身可以在信号处理程序中调用。这排除了对 pthread_atfork 的任何非平凡使用,即使它的 RATIONALE 明确提到在子进程中解锁互斥锁和重新创建线程!
我认为还存在不同可能解释的“灰色区域”:
1. 对于在已知不会在信号处理程序中调用 fork 的程序中的 pthread_atfork 处理程序。 2. 对于在不在信号处理程序中的 fork 调用周围发生的非 pthread_atfork 操作。
但是对于可移植应用程序来说,应该使用哪种解释是非常清楚的。

2

在fork()之后执行exec()是安全的,前提是互斥锁由将被exec()替换的程序所拥有。如果互斥锁是库的一部分,并且正在保护必须串行访问的资源,则应调用pthread_atfork()来注册回调:

  1. 一个函数,在fork本身之前并在调用fork()系统调用的线程上下文中调用。通常,此函数将获取保护关键部分的互斥锁,从而保证在fork期间没有线程位于关键部分内。
  2. 一个函数,在调用fork()的线程的上下文中,在子进程创建之后但fork()系统调用返回之前调用。然后,此函数可以解锁父进程中的互斥锁。
  3. 一个函数,在子进程的线程上下文中(如果/当进程分叉时)调用-在子进程创建之后但fork()系统调用返回之前调用。然后,子进程可以解锁其互斥锁的副本。

POSIX标准限制了在fork()和exec()之间允许的系统调用类型,只允许所谓的异步信号安全系统调用。创建线程没有明确列为异步信号安全系统调用,因此POSIX不允许子进程在fork()和exec()之间创建线程。具有讽刺意味的是,解锁互斥锁也没有明确列为异步信号安全系统调用,因此如果fork()的目的是执行exec(),则在子进程中严格来说也不允许解锁互斥锁 - 这可能是标准中的一个疏漏。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接