ForkingMixIn可以像ThreadingMixIn重用线程一样重用进程吗?

0 投票
2 回答
1376 浏览
提问于 2025-04-20 20:10

编辑:可能这个问题问得不太对。我是通过 threading.current_thread().ident 获取线程 ID 的,发现后续调用得到的值是一样的,这让我以为线程是被重用的,但这似乎并不是真正的线程 ID。通过 threading.current_thread().name,我发现每个请求的名字都不同,所以可能线程并没有被重用。此外,我在问题中提到的文档是关于 xmlrpc-c 库的,而不是 Python 的。问题是我需要从一个 xmlrpc-python 服务器与一个 xmlrpc-c 服务器进行通信。我的测试表明,当请求来自同一个连接时,xmlrpc-c 确实会重用线程(因为我可以利用线程缓存),但我并不完全确定。


抱歉标题有点奇怪,但我不知道怎么说得更清楚。

事情是这样的,当使用带有 ThreadingMixIn 的 XMLRPC 服务器时,它会为每个请求生成一个新线程,但会保持该连接一段时间,所以使用同一连接的所有请求都是由同一个线程来处理的。这是有上限的,默认情况下是同一连接可以处理 30 个请求。这个信息可以在 ServerAbyss 文档的“参数”部分找到:

http://xmlrpc-c.sourceforge.net/doc/libxmlrpc_server_abyss.html#server_abyss_run_server

重用连接的方法是使用同一个 ServerProxy 对象发送请求,而不是在调用之前立即创建一个新的对象,我认为这就是通常进行 RPC 调用的方式。

好吧,这种行为对我来说非常有用,因为它让我可以利用一些线程缓存。但真正的改进是希望能用 ForkingMixIn 实现这种行为,而不是 ThreadingMixIn。问题是,似乎 ForkingMixIn 总是会生成一个新进程来处理请求,而不考虑请求是否来自已经打开的连接。

那么,有没有办法让 ForkingMixIn 像 ThreadingMixIn 一样“重用”一个进程,以处理来自已打开连接的请求呢?

2 个回答

1

我觉得这里的问题在于如何把数据传递给进程。你可以把连接直接给进程(这样它就能自己处理所有事情),但这样你就永远不知道连接什么时候关闭或者不再需要了。而且,子进程在处理完N个请求后,也不容易再创建一个新的连接。

或者你可以把连接保留在父进程中,然后把数据通过管道传给子进程(比如通过标准输出到标准输入的管道)。但这样的话,你就得把每一个字节复制很多次(从父进程的接收缓冲区取出来,再放到父进程的管道发送缓冲区,可能在管道处理的时候还要复制几次,然后再复制到子进程的输入缓冲区……),每个结果的每个字节也要这样做。对于处理时间较长的小请求来说,这不是个大问题,但我觉得还是要留意一下。

2

socketserver.ForkingMixIn 不能做到这一点。

不过,socketserver.ThreadingMixIn 也做不到,所以你的前提是错误的。那么第一个问题是……你以为你在使用线程连接池,实际上并没有,而且你也没有发现问题,那你确定你真的需要它吗?


如果你确实需要,你就得自己写一个混合类。幸运的是,你可以继承 ForkingMixIn 来处理启动和管理进程的繁重工作,然后在上面添加你自己的代码,这样就能很顺利地使用唯一有用文档的那个方法,process_request

另外,像很多标准库一样,socketserver 既是可读的示例代码,也是一个有用的库,这就是为什么文档一开始就链接到 源代码,这样会让事情变得简单很多。


不幸的是,虽然把你的代码插入到 ForkingMixIn 中会很简单,但设计和编写这些代码就没那么容易了。

基本概念很简单:你有一个以连接为键的子进程缓存。对于每个请求,你在缓存中查找连接,如果需要就创建一个新的子进程,然后把请求传递给合适的子进程。

不幸的是,这些步骤说起来容易,做起来难。

其实第一步还好。那只是一个 WeakValueDictionary。但之后的事情就复杂了:

  • 什么是“连接”?XMLRPC 是一种纯粹的无状态请求-响应协议。如果你假设每个地址都是一个连接,你就会得到错误的结果(比如同时运行两个客户端,或者两个用户在同一个 NAT 内);如果你假设每个套接字都是一个连接,你又会漏掉一些(客户端可以并且会因为任何原因关闭套接字并重新打开一个新的)。通常的解决办法是使用存储在 HTTP cookie(或 HTTP 头或 XML 元素)中的应用级会话,所以你现在需要写或使用一个 HTTP 会话管理器(包括处理超时回收)。
  • 你怎么把请求“传递”给子进程?ForkingMixIn 依赖于请求在 fork 之前是一个局部变量,因此在 fork 之后仍然可以作为局部变量使用。但对于你从缓存中取出的某个对象,这就不成立了。所以,你需要使用 multiprocessing.QueuePipe,或者其他的进程间通信机制。而且,虽然 Queue 对于可以被序列化的对象来说很简单,但请求对象几乎肯定不能被序列化,因为它包含像套接字文件这样的东西。所以,你需要写代码把所有东西打包成可以传递的东西——这包括要么进行文件描述符迁移(Python 不会为你处理),要么读取所有请求数据以便传递(这样另一边就可以把它包裹在,比如说,BytesIO 中,放入 self.rfile)。
  • 子进程怎么回应?同样,ForkingMixIn 依赖于它从父进程继承了 wfile 文件,但这显然在这里行不通。如果你没有进行文件描述符迁移,那么你需要通过进程间通信把响应传回去(例如,另一个 Queue)。在这种情况下,父进程必须在那个进程间通信上阻塞。所以,你需要使用 ThreadingMixIn 为每个请求创建一个线程,以便它们可以等待你的子进程(或者使用其他机制,比如一个子服务线程,循环处理所有的进程间通信,作为可组合的未来或者可选择的管道等等)。

这些概念本身并不特别难(除了会话超时的问题),但所有这些都需要写很多代码。

撰写回答