混合线程,叉子和互斥体,我应该注意什么?

时间:2013-01-18 21:01:03

标签: multithreading fork mutex

如果我fork一个线程持有互斥锁的进程,如果我在孩子中立即exec,我相对安全吗?在exec之前,孩子可以安全地做些什么?

如果执行fork的线程然后孩子在调用exec之前继续释放互斥锁会导致问题吗?如果我尝试获取父进程在fork之前拥有的子进程中的互斥锁(并且可能或可能仍然拥有),会发生什么?

不同平台上的答案有何不同?我主要关注的是Unix变体,特别是Linux。但我对NT感到好奇。当然,NT(据我所知)并没有fork

2 个答案:

答案 0 :(得分:6)

有关在多线程环境中与fork相关的问题的讨论,请参阅pthread_atfork,尤其是RATIONALE部分。它还提示了一个孩子和父母在fork之前和之后应该有效的内容。

更新:RATIONALE部分是非规范性的,结果证明与标准的其他部分相冲突。有关详细信息,请参阅Dave Butenhof的this defect report

exec之后的立即fork应该对任何多线程程序状态(即任何持有任何互斥锁的线程)都是安全的。截至forkexec之间的情况,情况很复杂:

最重要的是,在子进程中只复制了一个线程(称为fork)。因此,fork另一个线程持有的任何互斥锁都会永久锁定。那就是(假设非进程共享的互斥锁)子进程中的 copy 永远被锁定,因为没有线程可以解锁它。

fork之后释放互斥锁是可行的,也就是说,如果fork线程首先拥有互斥锁。这就是pthread_atfork处理程序通常的工作方式:在fork之前锁定互斥锁,在子中解锁并在父级中解锁

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

通过注册相应的PTHREAD_MUTEX_RECURSIVE处理程序,第三方库可以保证在pthread_atforkfork之间安全使用。 (我希望它主要来自编程语言运行时,而不是通用库)。


经过一些更多的研究,我建议避免exec上依赖以任何方式,除了pthread_atfork和{{之间的异步信号安全呼叫之外什么都不做1}}(放弃fork / exec fork会更好。)

问题是,exec本身可以在信号处理程序中调用。它排除了posix_spawn的任何重要使用,即使它的RATIONALE明确提到解锁互斥锁并在子进程中重新创建线程(!)。

我认为不同可能的解释的“灰色地带”仍然存在:

  1. 对于已知的程序中的fork处理程序,永远不要在信号处理程序中调用pthread_atfork
  2. 对于信号处理程序中pthread_atfork调用周围发生的非pthread-atfork操作。
  3. 但很清楚哪种读数将用于便携式应用。

答案 1 :(得分:1)

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

  1. 在fork本身之前以及在进行fork()系统调用的线程的上下文中调用的函数。通常,此函数将获取保护关键部分的互斥锁的锁定,从而保证在fork期间没有线程位于关键部分内
  2. 在创建子进程之后但在fork()系统调用返回之前,在线程调用fork()的上下文中调用的函数。然后,此函数可以解锁父进程中的互斥锁。
  3. 要在子进程的线程上下文中调用的函数,如果/当进程分叉时 - 在创建子进程之后但在fork()系统调用返回之前调用它。然后,子进程可以解锁其互斥锁的副本。
  4. POSIX标准将fork()之后和exec()之前允许的系统调用类型限制为所谓的async-signal-safe系统调用。创建线程未明确列为异步信号安全系统调用,因此POSIX不允许子进程在fork()之后和exec()之前创建线程。具有讽刺意味的是,解锁互斥锁并未明确列为异步信号安全系统调用,并且如果fork()的意图是exec,则在子进程中的fork()之后并不严格允许它。 ) - 可能是标准中的疏忽。