Python多进程
一、multiprocessing模块提供了一个创建进程的类Process,其创建进程有两种方法:
1、创建一个Process类的实例,并指定目任务函数:
示例代码:
1 from multiprocessing import Process 2 import os 3 import time 4 5 def task_process(delay): 6 num = 0 7 for i in range(delay*100000000): 8 num += 1 9 print(f"进程pid为{os.getpid()},执行完成") 10 11 if __name__ == '__main__': 12 print(f"父进程pid为:{os.getpid()}") 13 t0 = time.time() 14 task_process(3) 15 task_process(3) 16 t1 = time.time() 17 print(f"顺序执行耗时{t1-t0}") 18 p0 = Process(target=task_process,args=(3,)) 19 p1 = Process(target=task_process, args=(3,)) 20 t2 = time.time() 21 p0.start();p1.start() 22 p0.join();p1.join() 23 t3 = time.time() 24 print(f"多进程并发执行耗时{t3-t2}")
返回内容:
父进程pid为:13008
进程pid为13008,执行完成
进程pid为13008,执行完成
顺序执行耗时48.99575757980347
进程pid为13784,执行完成
进程pid为6468,执行完成
多进程并发执行耗时32.62778663635254
2、自定义一个类并继承Process类,重写其__init__()方法和run()方法:
示例代码:
1 from multiprocessing import Process 2 import os 3 import time 4 5 class MyProcess(Process): 6 def __init__(self,delay): 7 self.delay = delay 8 super().__init__() 9 def run(self): 10 num = 0 11 for i in range(self.delay * 100000000): 12 num += 1 13 print(f"进程pid为{os.getpid()},执行皮完成") 14 15 if __name__ == '__main__': 16 print(f"父进程pid为:{os.getpid()}") 17 p0 = MyProcess(3) 18 p1 = MyProcess(3) 19 t0 = time.time() 20 p0.start();p1.start() 21 p0.join();p1.join() 22 t1 = time.time() 23 print(f"多进程并发执行耗时{t1-t0}")
返回内容:
父进程pid为:6784
进程pid为14164,执行皮完成
进程pid为7128,执行皮完成
多进程并发执行耗时34.326029539108276
二、进程并发控制之Semaphore
Semaphore用来控制对共享资源的访问数量,可以控制同一时刻并发的进程数。
示例代码:
1 import multiprocessing,time 2 def worker(s,i): 3 s.acquire() 4 print(time.strftime('%H:%M:%S'),multiprocessing.current_process().name+" 获得运行锁") 5 time.sleep(i) 6 print(time.strftime('%H:%M:%S'), multiprocessing.current_process().name + " 释放运行锁") 7 s.release() 8 9 if __name__ == "__main__": 10 s = multiprocessing.Semaphore(2) #同一时刻只有两个进程在执行操作 11 for i in range(6): 12 p = multiprocessing.Process(target=worker,args=(s,2)) 13 p.start()
运行结果:
15:26:12 Process-1 获得运行锁
15:26:12 Process-2 获得运行锁
15:26:14 Process-1 释放运行锁
15:26:14 Process-2 释放运行锁
15:26:14 Process-5 获得运行锁
15:26:14 Process-3 获得运行锁
15:26:16 Process-5 释放运行锁
15:26:16 Process-3 释放运行锁
15:26:16 Process-4 获得运行锁
15:26:16 Process-6 获得运行锁
15:26:18 Process-4 释放运行锁
15:26:18 Process-6 释放运行锁
三、进程同步之Lock:
多进程目的是并发执行,提高资源利用率,从而提高效率,但有时候我们需要在某一时间只能有一个进程访问某个共享资源的话,就需要使用锁Lock.
示例代码一,不加锁:
1 import multiprocessing,time 2 def task1(): 3 n = 5 4 while n > 1: 5 print(f"{time.strftime('%H:%M:%S')} task1 输出信息") 6 time.sleep(1) 7 n -= 1 8 9 def task2(): 10 n = 5 11 while n > 1: 12 print(f"{time.strftime('%H:%M:%S')} task2 输出信息") 13 time.sleep(1) 14 n -= 1 15 16 def task3(): 17 n = 5 18 while n > 1: 19 print(f"{time.strftime('%H:%M:%S')} task3 输出信息") 20 time.sleep(1) 21 n -= 1 22 23 if __name__ == "__main__": 24 p1 = multiprocessing.Process(target=task1) 25 p2 = multiprocessing.Process(target=task2) 26 p3 = multiprocessing.Process(target=task3) 27 p1.start() 28 p2.start() 29 p3.start()
返回结果:
15:43:47 task1 输出信息
15:43:47 task2 输出信息
15:43:47 task3 输出信息
15:43:48 task1 输出信息
15:43:48 task2 输出信息
15:43:48 task3 输出信息
15:43:49 task1 输出信息
15:43:49 task2 输出信息
15:43:49 task3 输出信息
15:43:50 task1 输出信息
15:43:50 task2 输出信息
15:43:50 task3 输出信息
从返回结果可以看出,同一时刻有两个或三个进程在运行,如果需要同一时刻只允许一个进程运行,可以改成一面的代码:
示例代码二,加锁:
1 import multiprocessing,time 2 def task1(lock): 3 with lock: #和下文的lock.acquire()同样 4 n = 5 5 while n > 1: 6 print(f"{time.strftime('%H:%M:%S')} task1 输出信息") 7 time.sleep(1) 8 n -= 1 9 10 def task2(lock): 11 lock.acquire() 12 n = 5 13 while n > 1: 14 print(f"{time.strftime('%H:%M:%S')} task2 输出信息") 15 time.sleep(1) 16 n -= 1 17 lock.release() 18 19 def task3(lock): 20 lock.acquire() 21 n = 5 22 while n > 1: 23 print(f"{time.strftime('%H:%M:%S')} task3 输出信息") 24 time.sleep(1) 25 n -= 1 26 lock.release() 27 if __name__ == "__main__": 28 lock = multiprocessing.Lock() 29 p1 = multiprocessing.Process(target=task1,args=(lock,)) 30 p2 = multiprocessing.Process(target=task2,args=(lock,)) 31 p3 = multiprocessing.Process(target=task3,args=(lock,)) 32 p1.start() 33 p2.start() 34 p3.start()
返回结果:
15:51:03 task1 输出信息
15:51:04 task1 输出信息
15:51:05 task1 输出信息
15:51:06 task1 输出信息
15:51:07 task2 输出信息
15:51:08 task2 输出信息
15:51:09 task2 输出信息
15:51:10 task2 输出信息
15:51:11 task3 输出信息
15:51:12 task3 输出信息
15:51:13 task3 输出信息
15:51:14 task3 输出信息
从结要可以看出同一时刻,只有一个进程在运行。
四、进程同步之Event:
Event是用来实现进程之间同步通信的:
示例代码:
1 import multiprocessing,time 2 3 def wait_for_event(e): 4 e.wait() 5 time.sleep(1) 6 #唤醒后清除Event状态,为后续继续等待 7 e.clear() 8 print(f"{time.strftime('%H:%M:%S')} 进程 A:我们是兄弟,我等你...") 9 e.wait() 10 print(f"{time.strftime('%H:%M:%S')} 进程 A:好的,是兄弟一起走") 11 def wait_for_event_timeout(e,t): 12 e.wait() 13 time.sleep(1) 14 # 唤醒后清除Event状态,为后续继续等待 15 e.clear() 16 print(f"{time.strftime('%H:%M:%S')} 进程 B:好吧,我最多等你{t}秒") 17 e.wait(t) 18 print(f"{time.strftime('%H:%M:%S')} 进程 B:{t}秒时间到,我继续往前走了") 19 if __name__ == "__main__": 20 e = multiprocessing.Event() 21 w1 = multiprocessing.Process(target=wait_for_event,args=(e,)) 22 w2 = multiprocessing.Process(target=wait_for_event_timeout, args=(e,5)) 23 w1.start() 24 w2.start() 25 #主进程发话: 26 print(f"{time.strftime('%H:%M:%S')} 主进程:谁等我下,我需要8s时间") 27 #唤醒进程: 28 e.set() 29 time.sleep(8) 30 print(f"{time.strftime('%H:%M:%S')} 主进程:好了,我赶上了") 31 #再次唤醒等待的进程: 32 e.set() 33 w1.join() 34 w2.join() 35 print(f"{time.strftime('%H:%M:%S')} 主进程:退出")
返回结果:
16:21:05 主进程:谁等我下,我需要8s时间
16:21:06 进程 A:我们是兄弟,我等你...
16:21:06 进程 B:好吧,我最多等你5秒
16:21:11 进程 B:5秒时间到,我继续往前走了
16:21:13 主进程:好了,我赶上了
16:21:13 进程 A:好的,是兄弟一起走
16:21:13 主进程:退出
五、进程优先级队列Queue
Queue是多进程安全队列,可以实现多进程之间的数据传递。它主要有put和get两个方法:
put:用以插入数据到队列中,有两个可选参数:
blocked:默认值为True,如果设置了timeout,则该方法会阻塞其指定的时间,直到该队列有剩余的空间。如果超时,则会抛出Queue.Full异常。如果blocked为False,则该Queue已满,则立即抛出Queue.Full异常。
timeout:超时时间
get:可以从队列读取并删除一个元素,有两个可选参数:
blocked:默认值为True,如果设置了timeout,在等时间内没有取到任何元素,则会抛出Queue.Empty异常。如果blocked为False,会有两种情况:Queue有一个值可用,立即返回该值,否则队列为空,立即抛出Queue.Empty异常。
timeout:超时时间
示例代码(使用多进程实现生产者-消费者模式):
1 from multiprocessing import Process,Queue 2 import time 3 4 def ProducerA(q): 5 count = 1 6 while True: 7 q.put(f"产品 {count}") 8 print(f"{time.strftime('%H:%M:%S')} A 放入:[产品 {count}]") 9 count += 1 10 time.sleep(1) 11 def ConsumerB(q): 12 while True: 13 print(f"{time.strftime('%H:%M:%S')} B 取出:[{q.get()}]") 14 time.sleep(5) 15 if __name__ == "__main__": 16 q = Queue(maxsize=5) #队列最大容量是5 17 p = Process(target=ProducerA,args=(q,)) 18 c = Process(target=ConsumerB,args=(q,)) 19 c.start() 20 p.start() 21 c.join() 22 p.join()
返回结果:
17:02:37 A 放入:[产品 1]
17:02:37 B 取出:[产品 1]
17:02:38 A 放入:[产品 2]
17:02:39 A 放入:[产品 3]
17:02:40 A 放入:[产品 4]
17:02:41 A 放入:[产品 5]
17:02:42 B 取出:[产品 2]
17:02:42 A 放入:[产品 6]
17:02:43 A 放入:[产品 7]
17:02:47 B 取出:[产品 3]
17:02:47 A 放入:[产品 8]
17:02:52 B 取出:[产品 4]
17:02:52 A 放入:[产品 9]
17:02:57 B 取出:[产品 5]
17:02:57 A 放入:[产品 10]
17:03:02 B 取出:[产品 6]
17:03:02 A 放入:[产品 11]
...
从结果可以看出,生产者不停地生产,消费者不停地取出。当队列满时,生产者等待、当队列空时消费者等待。生产和消费的速度可能不一致,便使用Queue可以让生产者和消费者有条不紊地一直进行下去。从上面结果可以看出,生产者A生产的速度较快,当队列满时,等待消费者B取出后继续放入。
六、多进程之进程池Pool
在使用Python进行系统管理时,特别时同时操作多个文件目录或远程控制多台主机并行操作,可以节约大量的时间。当被操作的对象数目不大时,可以直接利用multiprocessing中的Process动态生成多个进程,十几个还好,但如果上百个、上千个目标,手动限制进程数量又太过烦琐,此时就可以发挥进程池的功效了。
Pool可以提供指定数量的进程供用户调用,当有新的请求提交到Pool中时,如果池还没有满,就会创建一个新的进程用于执行请求;如果池中的进程数量已达到规定的最大值,该请求就会等待,直到池中有进程结束才会创建新的进程。
示例代码:
1 import multiprocessing 2 import time 3 4 def task(name): 5 print(f"{time.strftime('%H:%M:%S')}:{name} 开始执行") 6 time.sleep(3) 7 8 if __name__ == "__main__": 9 pool = multiprocessing.Pool(processes=3) #同时最多只有三个进程执行 10 for i in range(10): 11 pool.apply_async(func=task,args=(i,)) 12 pool.close() 13 pool.join() 14 print("over")
返回结果:
17:27:44:0 开始执行
17:27:44:1 开始执行
17:27:44:2 开始执行
17:27:47:3 开始执行
17:27:47:4 开始执行
17:27:47:5 开始执行
17:27:50:6 开始执行
17:27:50:7 开始执行
17:27:51:8 开始执行
17:27:53:9 开始执行
over
从执行结果来看同一时刻只有三个进程在执行,使用Pool实现了对进程并发数的限制。
七、多进程之数据交换Pipe
在类Unix系统中经常会使用到管道(Pipe)命令来让一条命令的输出(STDOUT)作为另一条命令的输入(STDIN)。在Python多进程编程中也有一个Pipe方法可以帮助我们实现多进程之间的数据传输。我们可以将Unix中的一个命令比作一个进程,一个进程的输出可以作为另一个进程的输入。
multiprocessing.Pipe()方法返回一个管道的两个端口,如Command1的STDOUT和Command2的STDIN,这样Command1的输出就作为Command2的输入。如果返过来,让Command2的输出也可以作为Command1的输入,这就是全双工管道,默认就是全双工管道。如果想设置半双工管道,只需要给Pipe()方法传递参数duplex=False就可以了,即Pipe(duplex=False)。
Pipe()方法返回的对象具有发送消息send()方法和接收消息recv()方法,可以调用Command1.send(msg)发送消息,调用Command2.recv()接收消息。如果没有消息可接收,recv()方法会一直阻塞。如果管道已经被关闭,recv()方法就会抛出异常EOFError。
示例代码:多进程全双工管道:
1 import multiprocessing 2 import time 3 4 def task1(pipe): 5 for i in range(5): 6 str = f"task1-{i}" 7 print(f"{time.strftime('%H:%M:%S')} task1 发送:{str}") 8 pipe.send(str) 9 time.sleep(2) 10 for i in range(5): 11 print(f"{time.strftime('%H:%M:%S')} task1 接收:{pipe.recv()}") 12 13 def task2(pipe): 14 for i in range(5): 15 print(f"{time.strftime('%H:%M:%S')} task2 接收:{pipe.recv()}") 16 time.sleep(1) 17 for i in range(5): 18 str = f"task2-{i}" 19 print(f"{time.strftime('%H:%M:%S')} task2 发送:{str}") 20 pipe.send(str) 21 22 if __name__ == "__main__": 23 pipe = multiprocessing.Pipe() 24 p1 = multiprocessing.Process(target=task1,args=(pipe[0],)) 25 p2 = multiprocessing.Process(target=task2,args=(pipe[1],)) 26 p1.start() 27 p2.start() 28 29 p1.join() 30 p2.join()
程序定义了两个了进程函数:task1先发送2条消息,再接收消息;task2先接收消息,再发送消息,运行结果如下:
18:42:14 task1 发送:task1-0
18:42:14 task1 发送:task1-1
18:42:14 task1 发送:task1-2
18:42:14 task1 发送:task1-3
18:42:14 task1 发送:task1-4
18:42:14 task2 接收:task1-0
18:42:14 task2 接收:task1-1
18:42:14 task2 接收:task1-2
18:42:14 task2 接收:task1-3
18:42:14 task2 接收:task1-4
18:42:15 task2 发送:task2-0
18:42:15 task2 发送:task2-1
18:42:15 task2 发送:task2-2
18:42:15 task2 发送:task2-3
18:42:15 task2 发送:task2-4
18:42:16 task1 接收:task2-0
18:42:16 task1 接收:task2-1
18:42:16 task1 接收:task2-2
18:42:16 task1 接收:task2-3
18:42:16 task1 接收:task2-4
以上内容来自郑征的《Python自动化运维快速入门》,清华大学出版社。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具