关于fork:多进程fork的陷阱
零、释义milvus:向量数据库langchain:python提醒工程框架一、背景本篇文章基于一个BUG的排查和解决过程,试图还原在某些场景下多过程编程的【陷阱】,达到前事不忘;后事之师的成果。程序基于python,但论断和情理实用于所有语言二、BUG问题体现最近的一段提醒工程相干的python代码,在不同操作系统的状况下,体现不一样 在macos零碎与linux零碎的单过程、macos零碎的多过程状况下均能够失常运行:在linux的多过程状况下会卡在与milvus交互的中央,如下图 三、假如milvus服务端导致(磁盘满了、内存满了、服务忙碌等)网络异样导致操作系统导致连贯milvus所用的底层调用包导致四、假如验证和BUG排查思路❌【milvus服务端导致】 独自测试了milvus的读写,服务自身没有问题,milvus所在服务器也是衰弱状态,不存在资源不足的状况。排除1❌【网络异样导致】 telnet端口通,ping通且稳固,网络是OK的。排除2✅【操作系统导致】✅【连贯milvus所用的底层调用包导致】 初步判断为多过程导致的问题,那么为何macos中的多过程失常,linux零碎的多过程就有问题呢?排查思维链(Chain-of-Thought) 于是翻阅python对于多过程模块的官网文档,直到看到了这样一段话 python多过程在不同操作系统,默认启动子过程的形式是不一样的,在windows和macos上,默认应用【spawn】,而在linux上,默认是用【fork】,那么问题很有可能出在这两种不同的启动形式上。本着控制变量法的debug形式,我在linux上将子过程的启动形式指定为了【spawn】,✅问题解决,程序胜利运行至此,尽管外表上问题解决了, 但我对解决此BUG的播种只有:【spawn】大法好,对其余稍深层次的细节无所不知,遗留有一些关键问题: spawn是什么fork是什么为什么针对此BUG,spawn能够,fork不行如果咱们偏要用fork来做,行不行,怎么做?于是,又回过头认真看了官网文档介绍以及 python官网issue讨论区,(如下图) spawn与fork概念如下 spawn:从头构建一个子过程,父过程的数据等拷贝到子过程空间内,领有本人的Python解释器,所以须要从新加载一遍父过程的包,因而启动较慢,因为数据都是本人的,安全性较高fork:除了必要的启动资源外,其余变量,包,数据等都继承自父过程,并且是copy-on-write的,也就是共享了父过程的一些内存页,因而启动较快,然而因为大部分都用的父过程数据,所以是不平安的过程fork有可能导致不平安的过程,是因为fork用到copy-on-write技术,会继承父过程的数据和堆栈,由此导致一些不平安的问题。那么针对此BUG,具体是哪个中央导致了不平安呢? 既然是milvus连贯出了错,那先从连贯下手,排查发现,首先,主过程所在文件在import模块的时候,其中一个模块(文件)发动了一次milvus的连贯,如下图 而后,主过程开始启动子过程(fork),子过程调用langchain的milvus模块,langchain中milvus连贯初始化的代码是这样写的 子过程在上图中的步骤2的时候卡住,经排查是因为子过程基本没有连上milvus,然而步骤1明明曾经判断过,如果没有连贯,则创立。再进一步看看connections.has_connection("default")这个函数,如下图 函数会判断self._connected_alias变量中是否有记录,进一步看看这个变量怎么来的 在连贯milvus时,程序保护一个self._connected_alias变量来记录是否存在连贯,connections.has_connection("default")函数只是去self._connected_alias中查看是否有连贯记录,至此发现问题关键所在,父过程在第一次连贯milvus的时候,程序在self._connected_alias变量中记录了连贯信息,当fork子过程的时候,self._connected_alias变量被一并继承给了子过程,而当子过程应用connections.has_connection("default")函数判断与milvus的连贯状态的时候,发现了从父过程继承过去的self._connected_alias变量的已连贯信息,于是判断为已有连贯,导致子过程在理论没有连贯milvus的状况下间接加载milvus的数据,引发谬误。五、解决方案解决方案1计划 采纳spawn形式启动子过程长处 简略粗犷,子过程和父过程独立,数据隔离,过程平安拓展和保护绝对不便,不必放心相似的BUG有余 spawn形式,会老老实实地copy父过程的数据(即便不须要),比拟占内存空间,启动会慢一些解决方案2计划 采纳fork形式启动子过程,须要对代码做如下批改 如果能够删除主过程中连贯milvus的代码 将milvus连贯工作都放到子过程中做如果不能删除主过程中连贯milvus的代码 在子过程判断与milvus是否已连贯的时候,不采纳connections.has_connection("default")函数,而是查看本过程本身的套接字连贯,防止来自父过程继承脏数据的净化,须要新增have_socket函数,做法如下def have_socket(): have_socket = False process_netstat = psutil.Process(os.getpid()) for _socket in process_netstat.connections(): if _socket.raddr.port == MILVUS_PORT: have_socket = True return have_socketif not have_socket(): connections.connect(**connection_args)长处 采纳fork,子过程启动快,通过优化代码逻辑,防止过程不平安的状况有余 后续的代码拓展和保护都要留神代码逻辑,防止相似BUG六、总结写多线程/多过程代码的时候,须要留神具体代码逻辑,防止继承的脏数据导致线程/过程不平安对于资源束缚不大,性能要求不高的场景,多过程一律用spawn七、号外【python开发组音讯】将spawn在所有平台上设置为默认选项曾经提上日程 ,打算3.14版本正式上线 https://discuss.python.org/t/switching-default-multiprocessin...【fork的长处和利用场景】fork也不是一无是处,对于只读数据须要共享的状况,还是十分省内存资源, 比方编写模型预测的并发服务,fork只加载1份模型到内存,而spawn会加载N份,gunicorn的-preload参数就是基于fork的copy-on-write技术,达到模型只加载一次的目标In general, fork is bad, but it's also convenient and people rely on it to prepare data in a main process and then "duplicate" the process to inherit cooked data. -Victor Stinner版本信息python3.11.4langchain==0.0.146ReferencesPython crashes on macOS after fork with no execmultiprocessing's default posix start method of 'fork' is broken: change to 'spawn’Multiprocessing causes Python to crash and gives an error may have been in progress in another thread when fork() was called机器学习模型API多过程内存共享写时复制https://docs.python.org/3/library/multiprocessing.htmlhttps://discuss.python.org/t/switching-default-multiprocessin...