【问题标题】:Correct way of calling fork() after parent has created threads?在父级创建线程后调用 fork() 的正确方法?
【发布时间】:2017-07-29 10:49:15
【问题描述】:

我正在实现一个采用第三方插件的复杂应用程序,我想在子进程中运行插件代码以进行隔离。父进程需要是多线程的,但我读过fork may be unsafe in multithreaded processes,特别是如果你不立即调用execve,还有pthread_atfork is not a complete solution

其他复杂的应用程序对此做了什么?我知道 Chrome 同时使用子进程和多线程,所以它一定是可能的

【问题讨论】:

  • 你知道select()/poll()吗?你在用吗?
  • @Olaf 我认为这个问题被更恰当地标记为 POSIX 和 既不是 C 也不是 C++。它是关于两个操作系统功能交互方式的详细信息,适用于任何基于相同原语构建的编程语言。
  • @zwol 也许吧。我认为不清楚 OP 具体问了什么。
  • @Olaf 我刚刚开始使用这些东西,我主要使用 C++。但是 fork() 和 dlopen() 是系统调用,这就是我添加 C 标记的原因,
  • @IharobAlAsimi 我不知道 select() 和 poll()。我会检查这些。谢谢!

标签: multithreading fork posix


【解决方案1】:

如果您不能确保fork 仅在安全情况下使用,如John Bollinger's answer 中所述,一般的解决方法是使用“分叉服务器”。在创建任何线程之前,原始进程会分叉一次。子进程是fork server;它仍然是单线程的。父进程现在继续并创建它的线程。每当父级想要调用fork 时,它都会向分叉服务器发送一条消息,要求它这样做。

如果(最终)子进程也需要与父进程通信,最简单的方法是让父进程为每个子进程的 stdin 和 stdout 创建管道,然后将这些管道的子进程转移到 fork服务器,使用SCM_RIGHTS special message。您可以同时发送文件描述符和数据。分叉服务器和父服务器之间的通信协议可能需要变得非常漂亮——查看posix_spawn API 以获得您可能需要的所有旋钮的或多或少的完整列表。 (注意:posix_spawn 只是 fork 的一个库包装器;使用它不会避免原来的问题。)

fork 服务器还负责调用waitpid 并将退出状态转发回父级。这比它应该的更棘手,因为用于等待几个可能事件(selectpoll)中的下一个事件的标准 API 不接受进程 ID 作为等待的事情之一。 (BSD 的 kqueue 可以,但你可能不是在 BSD 上。)你必须与 SIGCHLD 和一个 pipe-to-self 做一场混乱的舞蹈。

【讨论】:

  • 这似乎是一个有趣的解决方案。这个新的“分叉服务器”是否也将负责将来自子节点的任何信息传递给“原始父节点”?最后的孩子将如何在不依赖套接字的情况下与原始父母进行通信......或者可能是命名管道?这变得相当笨拙:)
  • @user2624119 我会在答案中添加一些关于此的内容。
【解决方案2】:

fork() 在多线程程序中的行为是明确定义的。成功时,子进程只有一个线程——与父程序中调用fork() 的线程相同。虽然这可能是个问题,但它是否真的个问题还要视情况而定。

fork() 何时会成为多线程程序的问题?

fork()ing 在多线程程序中出现问题的主要原因是子进程依赖于互斥体、条件变量等,无法再依赖其他线程进行操作。例如,如果子进程需要获取它尚未持有的进程私有互斥锁,则该互斥锁可能在分叉时由不同的线程持有。在这种情况下,它永远不会在子进程中释放,因为子进程中不存在可以释放它的线程。

什么时候fork()ing 不是 多线程程序的问题?强>

涉及fork() 的常见习语之一是立即通过执行另一个程序来跟进它。这没问题,不管父级的线程性如何。

或者,如果子进程不依赖于任何有问题的资源,则不需要做任何特别的事情。请注意,进程共享线程间对象在这个意义上不是“有问题的”。这种情况相当普遍,听起来可能就是您的情况。

否则,如果父级的分叉线程能够并且确实获得子级在分叉之前需要的所有进程私有线程间资源,这不是问题。在某些情况下,pthread_atfork() 注册的处理程序可以帮助解决此问题,但在其他情况下,在 fork 调用的直接环境中完成此操作更有意义。

总体

您提出的问题好像fork()ing 是多线程程序的一个深刻而麻烦的问题。这当然是一个应该考虑的问题,通常最好避免同时使用多线程和多进程。因此,既然您想要多个进程以便拥有单独的地址空间和名称空间来加载插件,那么也许您应该考虑在现在使用线程的任何地方使用单独的进程。另一方面,如果您稍加考虑和注意,您可能可以让您的多线程进程正常工作以派生子子并与它们交互。

【讨论】:

  • 感谢您的回答! “在 fork() 调用的直接环境中用餐”几乎是我为阻止程序崩溃所做的。在子代中克隆 fork 之前创建的父代中的 c++ std::thread 并且子代中的“死”线程对象超出范围而没有调用它。这导致了一个 c++ 异常,它说“在没有活动异常的情况下终止”。看来我有一些研究要做!谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-06
相关资源
最近更新 更多