理解Python multiprocessing.Process start和join方法

本文通过实例详细解析Python中多进程的start和join方法,展示如何创建和管理子进程,包括进程间的数据打印与同步控制。

以下代码来自:https://www.cnblogs.com/lipijin/p/3709903.html,这里只是将其中print转为python3方式,通过依次看这几段代码以及运行结果应该能够理解start和join方法,文字描述反而可能会造成误解。

In [1]: #encoding:utf-8 
   ...: from multiprocessing import Process 
   ...: import os, time, random 
   ...:  
   ...: #线程启动后实际执行的代码块 
   ...: def r1(process_name): 
   ...:     for i in range(5): 
   ...:         print(process_name, os.getpid())     #打印出当前进程的id 
   ...:         time.sleep(random.random()) 
   ...:  
   ...: def r2(process_name): 
   ...:     for i in range(5): 
   ...:         print(process_name, os.getpid())     #打印出当前进程的id 
   ...:         time.sleep(random.random()) 
   ...:      
   ...: if __name__ == "__main__": 
   ...:     print("main process run...") 
   ...:     p1 = Process(target=r1, args=('process_name1', ))       #target:指定进程执行的函数,args:该函数的参数,需要使用tuple 
   ...:     p2 = Process(target=r2, args=('process_name2', )) 
   ...:          
   ...:     p1.start()    #通过调用start方法启动进程,跟线程差不多。 
   ...:     p2.start()    #但run方法在哪呢?待会说。。。 
   ...:     p1.join()     #join方法也很有意思,寻思了一下午,终于理解了。待会演示。 
   ...:     p2.join() 
   ...:     print("main process runned all lines...") 
   ...:                                                                                                                                                                                                            
main process run...
process_name1 7713
process_name2 7714
process_name1 7713
process_name2 7714
process_name2 7714
process_name1 7713
process_name1 7713
process_name2 7714
process_name1 7713
process_name2 7714
main process runned all lines...
In [1]: #encoding:utf-8 
   ...: from multiprocessing import Process 
   ...: import os, time, random 
   ...:  
   ...: def r(): 
   ...:     print('run method') 
   ...:      
   ...: if __name__ == "__main__": 
   ...:     print("main process run...") 
   ...:     #没有指定Process的targt 
   ...:     p1 = Process() 
   ...:     p2 = Process() 
   ...:     #如果在创建Process时不指定target,那么执行时没有任何效果。因为默认的run方法是判断如果不指定target,那就什么都不做 
   ...:     #所以这里手动改变了run方法 
   ...:     p1.run = r 
   ...:     p2.run = r 
   ...:          
   ...:     p1.start() 
   ...:     p2.start() 
   ...:     p1.join() 
   ...:     p2.join() 
   ...:     print("main process runned all lines...") 
   ...:                                                                                                                                                                                                            
main process run...
run method
run method
main process runned all lines...
In [1]: #encoding:utf-8 
   ...: from multiprocessing import Process 
   ...: import os, time, random 
   ...:  
   ...: def r1(process_name): 
   ...:     for i in range(5): 
   ...:         print(process_name, os.getpid())     #打印出当前进程的id 
   ...:         time.sleep(random.random()) 
   ...: def r2(process_name): 
   ...:     for i in range(5): 
   ...:         print(process_name, os.getpid())     #打印出当前进程的id 
   ...:         time.sleep(random.random()) 
   ...:  
   ...: if __name__ == "__main__": 
   ...:     print("main process run...") 
   ...:     p1 = Process(target=r1, args=('process_name1', ))  
   ...:     p2 = Process(target=r2, args=('process_name2', ))  
   ...:  
   ...:     p1.start() 
   ...:     p2.start() 
   ...:     #p1.join() 
   ...:     #p2.join()     
   ...:     print("main process runned all lines...") 
   ...:                                                                                                                                                                                                            
main process run...
process_name1 7881
main process runned all lines...
process_name2 7882
process_name2 7882                                                                                                                                                                                         
process_name2 7882
process_name2 7882
process_name2 7882
process_name1 7881
process_name1 7881
process_name1 7881
process_name1 7881
In [1]: #encoding:utf-8 
   ...: from multiprocessing import Process 
   ...: import os, time, random 
   ...:  
   ...: def r1(process_name): 
   ...:     for i in range(5): 
   ...:         print(process_name, os.getpid())     #打印出当前进程的id 
   ...:         time.sleep(random.random()) 
   ...: def r2(process_name): 
   ...:     for i in range(5): 
   ...:         print(process_name, os.getpid())     #打印出当前进程的id 
   ...:         time.sleep(random.random()*2) 
   ...:  
   ...: if __name__ == "__main__": 
   ...:     print("main process run...") 
   ...:     p1 = Process(target=r1, args=('process_name1', ))  
   ...:     p2 = Process(target=r2, args=('process_name2', ))  
   ...:  
   ...:     p1.start() 
   ...:     p2.start() 
   ...:     p1.join() 
   ...:     #p2.join()     
   ...:     print("main process runned all lines...") 
   ...:                                                                                                                                                                                                            
main process run...
process_name1 7938
process_name2 7939
process_name1 7938
process_name1 7938
process_name1 7938
process_name2 7939
process_name1 7938
process_name2 7939
main process runned all lines...

process_name2 7939                                                                                                                                                                                         
process_name2 7939
In [1]: #encoding:utf-8 
   ...: from multiprocessing import Process 
   ...: import os, time, random 
   ...:  
   ...: def r1(process_name): 
   ...:     for i in range(5): 
   ...:         print(process_name, os.getpid())     #打印出当前进程的id 
   ...:         time.sleep(random.random()) 
   ...: def r2(process_name): 
   ...:     for i in range(5): 
   ...:         print(process_name, os.getpid())     #打印出当前进程的id 
   ...:         time.sleep(random.random()*2) 
   ...:  
   ...: if __name__ == "__main__": 
   ...:     print("main process run...") 
   ...:     p1 = Process(target=r1, args=('process_name1', ))  
   ...:     p2 = Process(target=r2, args=('process_name2', ))  
   ...:  
   ...:     p1.start() 
   ...:     p1.join() 
   ...:     p2.start() 
   ...:     #p2.join()     
   ...:     print("main process runned all lines...") 
   ...:                                                                                                                                                                                                            
main process run...
process_name1 7993
process_name1 7993
process_name1 7993
process_name1 7993
process_name1 7993
main process runned all lines...

process_name2 7994
process_name2 7994                                                                                                                                                                                        
process_name2 7994
process_name2 7994
process_name2 7994
<think> 嗯,用户的问题是询问Pythonmultiprocessing.Process是否会导致线程阻塞无法释放。让我结合用户提供的五个站内引用系统指令来分析这个问题。 从引用内容来看,用户显然遇到了多进程编程的实际问题。引用[1]提到用户原本想用多线程解决并发问题,后来改用multiprocessing.Process,但遇到了"进程启动模式""内存共享模式"的坑。引用[2]则明确指出join()方法使用不当会导致阻塞等待的问题。这说明用户的核心困惑在于阻塞机制。 用户引用的材料中,引用[2]的代码片段特别典型:p.join()的位置错误会导致主进程阻塞。引用[4]补充了另一个关键点——队列死锁问题,当子进程往队列put数据过多时可能锁住整个进程。而引用[5]提到的进程池机制则给出了替代方案。这些引用共同指向阻塞问题的三种典型场景:join阻塞、队列阻塞、进程池阻塞。 用户上次对话涉及快速排序的实现,这次突然转向多进程阻塞问题,可能是实际开发中遇到了性能瓶颈。考虑到Python的GIL限制,用户应该是在CPU密集型任务中尝试用多进程替代多线程,但缺乏对阻塞机制的理解。 需要特别注意引用[1]中用户的自我吐槽:"学艺不精"、"视野盲区",说明用户已经意识到基础概念的重要性。因此在回答中既要解释阻塞原理,更要给出具体的避坑指南。用户提供的引用[3]提到multiprocessing模块在Windows模拟fork的特性,这点也值得在回答中强调。 关于阻塞释放问题,本质上取决于三个因素:join方法调用位置、进程间通信机制、资源释放逻辑。必须提醒用户注意WindowsLinux下的差异,这在引用[3][4]中都有体现。特别是引用[4]提到的队列死锁案例,需要给出可操作的解决方案。 最后决定这样组织回答:先明确结论"可能阻塞,但可避免",然后分join阻塞、IPC阻塞、资源阻塞三类展开,每类都引用对应的文献并给出代码示例。特别注意用户引用[2]中的join位置错误案例,要作为重点警示。结尾的"相关问题"设计成阶梯式,从基础阻塞原理延伸到实战调试,这样既解答当前疑问,又预防用户后续可能遇到的问题。 </think> Python中`multiprocessing.Process`本身**不会导致线程阻塞无法释放**,但**使用不当**会导致主进程或子进程阻塞。关键在于正确管理进程生命周期进程间通信(IPC)。以下是核心要点常见陷阱: --- ### 一、导致阻塞的典型场景及解决方法 1. **`join()` 方法使用不当** - **问题**:在主进程中过早调用`p.join()`会阻塞主进程,直到子进程结束。若子进程因死锁或任务未完成而无法退出,主进程会永久阻塞[^2]。 - **解决**: - **非阻塞模式**:调用`p.start()`后,主进程继续执行其他任务,最后再调用`p.join()`等待子进程结束。 ```python processes = [] for _ in range(4): p = multiprocessing.Process(target=worker) p.start() processes.append(p) # 主进程先执行其他任务... do_other_work() # 最后等待所有子进程结束 for p in processes: p.join() # 此时阻塞 ``` - **超时机制**:`p.join(timeout=5)`设置超时避免永久阻塞。 2. **进程间通信(IPC)死锁** - **问题**:使用`Queue`或`Pipe`传输大量数据时,若生产者/消费者速度不匹配,队列满/空会导致进程阻塞[^4]。 - **解决**: - 使用`Queue.put(timeout=...)``Queue.get(timeout=...)`设置超时。 - 限制队列容量(`Queue(maxsize=100)`),避免数据积压。 - 改用`multiprocessing.Manager().Queue()`(跨进程安全但稍慢)。 3. **资源竞争导致死锁** - **问题**:多个进程竞争共享资源(如锁、文件)时,未正确释放资源会阻塞所有相关进程。 - **解决**: - 使用`with lock:`上下文管理器确保锁的释放。 - 避免嵌套锁,按固定顺序获取锁。 --- ### 二、进程阻塞与线程阻塞的区别 - **线程阻塞**:在Python中,多线程因GIL(全局解释器锁)无法真正并行,线程阻塞会影响同进程内其他线程。 - **进程阻塞**:每个子进程独立运行,阻塞**不会直接影响其他进程或主进程**(除非主进程调用`join()`等待)。 > ✅ 结论:`multiprocessing.Process`子进程阻塞不会导致主进程的线程阻塞,但主进程可能因等待子进程(如`join()`)而主动阻塞。 --- ### 三、进程阻塞的调试方法 1. **检查`join()`调用位置**:确保不在启动子进程后立即阻塞主进程[^2]。 2. **监控队列状态**: ```python if queue.full(): print("队列已满,可能阻塞生产者!") ``` 3. **日志追踪**:在子进程入口/出口打印日志,定位卡死位置。 4. **避免共享状态**:尽量使用消息传递(IPC)而非共享内存。 --- ### 四、替代方案:进程池(Pool) 使用`multiprocessing.Pool`可自动管理进程生命周期,避免手动阻塞问题[^5]: ```python from multiprocessing import Pool def worker(x): return x * x with Pool(4) as pool: # 非阻塞异步任务 result = pool.map_async(worker, range(10)) print("主进程继续执行...") results = result.get() # 需要时阻塞获取结果 ``` > **关键区别**:`Pool.map()`会阻塞主进程,而`Pool.map_async()` + `result.get()`可分离任务提交与结果获取。 --- ### 总结 | 场景 | 是否导致阻塞 | 解决方案 | |---------------------|------------------|----------------------------| | 主进程调用`join()` | 是(主进程阻塞) | 延迟`join()`或设置超时 | | 队列满/空 | 是(子进程阻塞) | 限制队列容量,设置超时 | | 资源竞争死锁 | 是(相关进程阻塞) | 使用上下文管理器,避免嵌套锁 | | 进程池`map()` | 是(主进程阻塞) | 改用`map_async()` + `get()` | ✅ **结论**:`multiprocessing.Process`设计上不会导致线程阻塞无法释放,阻塞问题源于代码逻辑错误(如`join`位置、IPC死锁)。通过合理设计进程通信、资源管理及生命周期控制可完全避免[^1][^2][^4][^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值