4

Twisted 文档让我相信,在同一个应用程序中结合 和 等技术是可以reactor.spawnProcess()threads.deferToThread(),reactor 可以在幕后优雅地处理这个问题。在实际尝试后,我发现我的应用程序死锁了。自己使用多个线程,或者自己使用子进程,一切都很好。

查看反应器源代码,我发现该SelectReactor.spawnProcess()方法只是简单地调用os.fork(),而不考虑可能正在运行的多个线程。这解释了死锁,因为从对您的调用开始,os.fork()将有两个进程运行多个并发线程,并且谁知道使用相同的文件描述符做什么。

我的问题是,解决这个问题的最佳策略是什么?

我想到的是 subclass SelectReactor,所以它是一个单例并且os.fork()只调用一次,在实例化时立即调用。子进程将在后台运行并充当父进程的服务器(使用管道上的对象序列化来来回通信)。父进程继续运行应用程序并且可以根据需要使用线程。在父进程中的调用spawnProcess()将委托给子进程,这将保证只有一个线程在运行,因此可以os.fork()安全地调用。

有没有人这样做过?有更快的方法吗?

4

4 回答 4

4

解决这个问题的最佳策略是什么?

提交描述问题的票(可能在注册之后),最好使用可重现的测试用例(以获得最大准确性)。然后可以讨论实现它的最佳方式(或方式 - 不同的平台可能需要不同的解决方案)。

之前已经提出了立即创建子进程以帮助进一步创建子进程的想法,以解决围绕子进程收获的性能问题。如果这种方法现在解决了两个问题,它开始看起来更具吸引力。这种方法的一个潜在困难是spawnProcess同步返回一个对象,该对象提供孩子的 PID 并允许向它发送信号。如果中间有中间进程,这需要更多的工作来实现,因为 PID 需要在spawnProcess返回之前传回给主进程。类似的挑战将支持该childFDs论点,因为不再可能仅继承子进程中的文件描述符。

另一种解决方案(可能更骇人听闻,但也可能具有较少的实现挑战)可能是在调用sys.setcheckinterval之前调用非常大的数字os.fork,然后仅在父进程中恢复原始检查间隔。这应该足以避免进程中的任何线程切换,直到os.execvpe发生,破坏所有额外的线程。这并不完全正确,因为它会使某些资源(例如互斥锁和条件)处于不良状态,但是您使用这些资源deferToThread并不常见,因此也许这不会影响您的情况。

于 2011-05-19T03:59:15.917 回答
2

Jean-Paul 在他的回答中给出的建议很好,但这应该有效(并且在大多数情况下有效)。

首先,Twisted 也使用线程来解析主机名,而且我肯定在 Twisted 进程中使用了子进程,它们也建立客户端连接。所以这在实践中是可行的。

第二,fork()不在子进程中创建多个线程。 根据描述的标准fork()

应使用单个线程创建进程。如果多线程进程调用 fork(),则新进程应包含调用线程的副本...

现在,这并不是说;没有潜在的多线程问题spawnProcess。该标准还说:

...为避免错误,子进程只能执行异步信号安全操作,直到调用其中一个 exec 函数...

而且我认为没有什么可以确保只使用异步信号安全操作。

因此,请更具体地说明您的确切问题,因为它不是克隆线程的子进程。

于 2011-05-19T07:49:21.567 回答
2

一段时间后回到这个问题,我发现如果我这样做:

reactor.callFromThread(reactor.spawnProcess, *spawnargs)

而不是这个:

reactor.spawnProcess(*spawnargs)

然后问题在我的小测试用例中消失了。Twisted 文档“Using Processes”中有一句话让我尝试这样做:“Twisted 中的大多数代码都不是线程安全的。例如,将数据从协议写入传输不是线程安全的。”

我怀疑让-保罗提到的其他人遇到这个问题可能会犯类似的错误。应用程序有责任强制在正确的线程中进行反应器和其他 API 调用。显然,除了极少数例外,“正确的线程”几乎总是主反应器线程。

于 2011-08-24T19:42:58.923 回答
1

Linux 上的 fork() 绝对让子进程只有一个线程。

我假设您知道,在 Twisted 中使用线程时,线程被允许调用的唯一 Twisted API 是 callFromThread?所有其他 Twisted API 只能从主反应器线程调用。

于 2011-06-27T15:20:06.077 回答