$ ls ~yifei/notes/

在子线程中 fork 会怎样?

Posted on:

Last modified:

按照 posix 的规范来说,fork 之后的进程中,应该只有调用 fork 的那个线程。但是实际上,所有 线程都在,只是除了调用 fork 的线程以外,其他线程都被冻结了,并不会执行。一般来说,在 fork 之后继续执行 exec* 的话,是不会有什么大问题的。

fork 复制的是整个进程的空间,锁也会被复制。

可能会引起问题的地方:如果在 fork 之前的进程中有锁,而且是被其他的线程持有的,那么 fork 之后的进程中,这个锁永远不会有人来释放了,导致新的进程中的线程永远处于等待锁的状态。

fork 还会把所有打开的文件,socket 等描述符都复制一份。

即使在单线程环境中,这也可能引起问题,因为两个进程可能开始争抢同一个资源。所以合理的方式 总是在新的子进程中打开资源(比如数据库),而不是打开资源之后再 fork 出子进程。

在 Python 中,multiprocessing 默认使用的是 fork, 但是还好我们可以选择使用 spawn. 参见参考 文献 3 和 4.

在 web server 和 rpc 中,多进程模式下 fork 是何时执行的呢?使用的 fork 还是 spawn? 全局 变量尤其是数据库链接会不会每次初始化?

在 RPC 服务中,有一种常见的模式,我们创建了一个 Handler 类,在脚本里面直接实例化了一个类, 在这个类的构造函数中初始化了数据库等资源的链接,然后把 my_hanlder.handle 函数交给框架来 作为入口函数,这样合理吗?

Google 的 gRPC 在这方面显然是一个反面的例子,本身他不是一个 Python 的库,内部使用了各种 奇奇怪怪的技术,在 Python 中使用的时候就遇到了各种问题,以至于没有一个很好的多进程模式。

在 web server 中,uwsgi/gunicorn 这些 process runner 又是怎样处理的呢?

  • uwsgi 默认会先 load 再 fork, 这样就会有一些问题,比如全局的数据库链接等等,但是也 可以改成 fork 之后再加载。
  • gunicorn 和 uwsgi 恰恰相反,默认会先 fork 再 load.

不管怎样,这两个都使用了非常简洁的 prefork 模型,而且在文档中明确说明了会采用那种模式, 非常优秀。

参考

  1. https://stackoverflow.com/questions/39890363/what-happens-when-a-thread-forks/39890957
  2. https://stackoverflow.com/questions/46439740/safe-to-call-multiprocessing-from-a-thread-in-python
  3. 还没看完
  4. 还没看完
  5. https://github.com/grpc/grpc/issues/16001
  6. https://eleme.github.io/blog/2016/eleme-python-soa/
  7. https://uwsgi-docs.readthedocs.io/en/latest/ThingsToKnow.html
  8. https://stackoverflow.com/questions/42439553/how-to-share-in-memory-resources-between-flask-methods-when-deploying-with-gunic
  9. https://instagram-engineering.com/copy-on-write-friendly-python-garbage-collection-ad6ed5233ddf
WeChat Qr Code

© 2016-2022 Yifei Kong. Powered by ynotes

All contents are under the CC-BY-NC-SA license, if not otherwise specified.

Opinions expressed here are solely my own and do not express the views or opinions of my employer.

友情链接: MySQL 教程站