Twisted文档让我相信在同一个应用程序中结合reactor.spawnProcess()
和threads.deferToThread()
等技术是可以的,反应堆可以优雅地处理这个问题。在实际尝试时,我发现我的应用程序死锁。通过自己使用多个线程,或者自己使用子进程,一切都很好。
查看反应堆源代码,我发现SelectReactor.spawnProcess()
方法只调用os.fork()
而不考虑可能正在运行的多个线程。这解释了死锁,因为从调用os.fork()
开始,您将有两个进程,其中多个并发线程正在运行,并且知道谁知道具有相同文件描述符的内容。
我对SO的问题是,解决这个问题的最佳策略是什么?
我想到的是子类SelectReactor
,因此它是一个单例,只在实例化时立即调用os.fork()
一次。子进程将在后台运行,并充当父进程的服务器(使用管道上的对象序列化来回通信)。父级继续运行应用程序,并可根据需要使用线程。对父进程中的spawnProcess()
的调用将被委托给子进程,该进程将保证只有一个线程正在运行,因此可以安全地调用os.fork()
。
以前有人这样做过吗?有更快的方法吗?
答案 0 :(得分:4)
解决此问题的最佳策略是什么?
File a ticket(也许在registering之后)描述问题,最好是带有可重复的测试用例(为了获得最大的准确性)。然后可以讨论一下最佳方式(或方法 - 不同平台可能需要不同的解决方案)来实现它。
之前已经提出了立即创建子进程以帮助进一步创建子进程的想法,以解决围绕子进程收获的性能问题。如果这种方法现在解决了两个问题,它开始看起来更具吸引力。这种方法的一个潜在困难是spawnProcess
同步返回一个对象,该对象提供孩子的PID并允许将信号发送给它。如果存在中间过程,则需要执行更多工作,因为在spawnProcess
返回之前需要将PID传送回主进程。类似的挑战将是支持childFDs
参数,因为仅仅继承子进程中的文件描述符将不再可能。
另一种解决方案(可能稍微有点hackish,但可能也有较少的实施挑战)可能是在调用sys.setcheckinterval
之前调用os.fork
一个非常大的数字,然后恢复原来的仅检查父进程中的间隔。这应该足以避免在os.execvpe
发生之前进程中的任何线程切换,从而破坏所有额外的线程。这不完全正确,因为它会使某些资源(例如互斥和条件)处于不良状态,但是使用这些deferToThread
并不常见,所以这可能不会影响你的情况
答案 1 :(得分:2)
Jean-Paul在他的回答中给出的建议是好的,但应工作(在大多数情况下也是如此)。
首先,Twisted也使用线程进行主机名解析,我肯定在Twisted进程中也使用了子进程,这些进程也进行了客户端连接。所以这可以在实践中发挥作用。
其次,fork()
不会在子进程中创建多个线程。 According to the standard describing fork()
,
应使用单个线程创建进程。如果多线程进程调用fork(),则新进程应包含调用线程的副本...
现在,这并不是说spawnProcess
存在没有潜在的多线程问题;该标准还说:
...为避免错误,子进程可能只执行异步信号安全操作,直到调用其中一个exec函数为止...
我认为没有什么可以确保只使用异步信号安全操作。
因此,请更具体地说明您的确切问题,因为它不是一个克隆线程的子进程。
答案 2 :(得分:2)
一段时间后回到这个问题,我发现如果我这样做:
reactor.callFromThread(reactor.spawnProcess, *spawnargs)
而不是:
reactor.spawnProcess(*spawnargs)
我怀疑Jean-Paul提到的其他人有这个问题可能会犯同样的错误。应用程序负责执行该reactor,并在正确的线程内进行其他API调用。显然,除了非常狭窄的例外,“正确的线程”几乎总是主要的反应堆线程。
答案 3 :(得分:1)
fork()肯定会让子进程只有一个线程。
我假设您知道,在Twisted中使用线程时,允许线程调用的唯一Twisted API是callFromThread吗?所有其他Twisted API只能从主反应堆线程调用。