如果我fork
一个线程持有互斥锁的进程,如果我在孩子中立即exec
,我相对安全吗?在exec
之前,孩子可以安全地做些什么?
如果执行fork
的线程然后孩子在调用exec
之前继续释放互斥锁会导致问题吗?如果我尝试获取父进程在fork
之前拥有的子进程中的互斥锁(并且可能或可能仍然拥有),会发生什么?
不同平台上的答案有何不同?我主要关注的是Unix变体,特别是Linux。但我对NT感到好奇。当然,NT(据我所知)并没有fork
。
答案 0 :(得分:6)
有关在多线程环境中与fork
相关的问题的讨论,请参阅pthread_atfork,尤其是RATIONALE部分。它还提示了一个孩子和父母在fork
之前和之后应该有效的内容。
更新:RATIONALE部分是非规范性的,结果证明与标准的其他部分相冲突。有关详细信息,请参阅Dave Butenhof的this defect report。
exec
之后的立即fork
应该对任何多线程程序状态(即任何持有任何互斥锁的线程)都是安全的。截至fork
和exec
之间的情况,情况很复杂:
最重要的是,在子进程中只复制了一个线程(称为fork
)。因此,fork
时另一个线程持有的任何互斥锁都会永久锁定。那就是(假设非进程共享的互斥锁)子进程中的 copy 永远被锁定,因为没有线程可以解锁它。
fork
之后释放互斥锁是可行的,也就是说,如果fork
线程首先拥有互斥锁。这就是pthread_atfork
处理程序通常的工作方式:在fork
之前锁定互斥锁,在子中解锁并在父级中解锁。
在获取fork之前拥有进程的互斥锁时(记住,我们在子地址空间中讨论副本):如果它是由{{>拥有的{{ 1}}线程,它是递归锁定(适用于fork
);如果它由另一个线程拥有,它将永远锁定并且无法重新获取。
通过注册相应的PTHREAD_MUTEX_RECURSIVE
处理程序,第三方库可以保证在pthread_atfork
和fork
之间安全使用。 (我希望它主要来自编程语言运行时,而不是通用库)。
经过一些更多的研究,我建议避免exec
上依赖以任何方式,除了pthread_atfork
和{{之间的异步信号安全呼叫之外什么都不做1}}(放弃fork
/ exec
fork
会更好。)
问题是,exec
本身可以在信号处理程序中调用。它排除了posix_spawn
的任何重要使用,即使它的RATIONALE明确提到解锁互斥锁并在子进程中重新创建线程(!)。
我认为不同可能的解释的“灰色地带”仍然存在:
fork
处理程序,永远不要在信号处理程序中调用pthread_atfork
。 pthread_atfork
调用周围发生的非pthread-atfork操作。但很清楚哪种读数将用于便携式应用。
答案 1 :(得分:1)
在fork()之后安装exec(),前提是互斥锁由exec()替换的程序所拥有。如果互斥锁是库的一部分并且正在保护必须以串行方式访问的资源,那么它应该调用pthread_atfork()来注册回调:
POSIX标准将fork()之后和exec()之前允许的系统调用类型限制为所谓的async-signal-safe系统调用。创建线程未明确列为异步信号安全系统调用,因此POSIX不允许子进程在fork()之后和exec()之前创建线程。具有讽刺意味的是,解锁互斥锁并未明确列为异步信号安全系统调用,并且如果fork()的意图是exec,则在子进程中的fork()之后并不严格允许它。 ) - 可能是标准中的疏忽。