一 生产者与消费者模型
生产者与消费者是一种面向对象的设计模式,主要作用是用于解决程序中生产和消费的供需场景问题的。
示例一

1 import time, random, os 2 from multiprocessing import Process, Queue 3 # IPC:进程间的通信,可以使用Queue来完成 4 5 def consumer(q): 6 """消费者""" 7 while True: 8 # 从队列中提取数据 9 res = q.get() 10 if res is None: break # 当队列中提取到结束信息时,结束当前while循环 11 time.sleep(random.randint(1, 3)) 12 print(f"{os.getpid()}吃{res}") 13 14 15 def producer(q): 16 """生产者""" 17 for i in range(2): 18 time.sleep(random.randint(1, 3)) 19 res = f"包子{i}" 20 q.put(res) # 把数据保存到队列中 21 print(f"{os.getpid()}生产了{res}") 22 23 24 def task(p_count, c_count): 25 # 相当于服务员 26 q = Queue() 27 # 生产者们: 即厨师 28 p_list = [] 29 for i in range(p_count): 30 p = Process(target=producer, args=(q,)) 31 p.start() 32 p_list.append(p) 33 34 # 消费者们: 即顾客 35 for i in range(c_count): 36 c1 = Process(target=consumer, args=(q,)) 37 c1.start() 38 39 for p in p_list: p.join() # 这里阻塞等待所有的生产者全部提交任务 40 41 # 当出现多个生产者与消费者时,结束信号就要随着消费者的数量来发送。 42 for _ in range(c_count): 43 q.put(None) # 发送一个结束信息给队列中 44 45 if __name__ == '__main__': 46 task(3, 3)
示例二【了解】
JoinableQueue可以创建可连接的共享进程队列,像是一个Queue对象,但JoinableQueue队列允许项目的使用者通知生产者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。

1 import time, random, os 2 3 from multiprocessing import Process, JoinableQueue 4 5 6 def consumer(jq): 7 """消费者""" 8 while True: 9 res = jq.get() # 从队列中提取数据 10 time.sleep(random.randint(1, 3)) 11 print(f"{os.getpid()}吃{res}") 12 jq.task_done() # 向q.join()发送一次信号, 证明一个数据已经被取走了 13 14 def producer(jq): 15 """生产者""" 16 for i in range(3): 17 time.sleep(random.randint(1, 3)) 18 res = f"包子{i}" 19 jq.put(res) # 把通信的数据保存到队列中 20 print(f"{os.getpid()}生产了{res}") 21 22 jq.join() # 生产完毕,使用此方法进行阻塞,直到队列中所有项目均被处理。 23 24 25 def task(p_count, c_count): 26 """任务流程""" 27 # 创建一个进程共享队列对象 28 jq = JoinableQueue() 29 # 创建生产者 30 p_list = [] 31 for i in range(p_count): 32 p = Process(target=producer, args=(jq,)) 33 p.start() 34 p_list.append(p) 35 36 # 创建消费者 37 for i in range(c_count): 38 c = Process(target=consumer, args=(jq,)) 39 # 设置消费者进程为守护进程 40 c.daemon = True 41 c.start() 42 43 # 开始 44 for p in p_list: p.join() 45 print('主进程') 46 47 48 if __name__ == '__main__': 49 task(3, 3)
二 进程间的数据共享【Manager】
多进程间的数据是独立在不同内存的,而线程之间的数据是共享。如何让进程间也能实现数据共享呢?
可以基于文件来完成进程间的数据共享。但需要我们手动操作文件来记录进程间的共享数据。
幸运的事python里面的multiprocessing模型已经内置实现了,那就是Manager对象。

1 from multiprocessing import Process, Manager, Lock 2 3 def func(data, lock): 4 with lock: 5 data["count"] -= 1 6 7 if __name__ == "__main__": 8 # 设置进程间要共享的数据 9 manager = Manager() 10 data = manager.dict({"count": 100}) # 表示在多个子进程之间共享一个字典数据 11 lock = Lock() 12 13 p_list = [] 14 for i in range(100): 15 p = Process(target=func, args=(data, lock)) 16 p.start() 17 p_list.append(p) 18 19 20 # 等待每一个进程执行完毕 21 for p in p_list: p.join() 22 23 print(data) # {'count': 0}
三 信号量【Semaphore】适用进程和线程
递归锁【RLock】:实现同一时间允许多个进程上多把锁,只允许同一时间只有一个进程或线程修改数据
信号量【Semaphore】:实现同一时间允许多个进程上多把锁,允许同一时间多个进程同时修改多个共享数据而且还要加锁。
实现原理是基于计数器+锁实现的,它允许同时给1个或多个进程上锁,当资源释放时计数器就会递增,当资源占用时计数器就会递减,多个进程可以通过操作信号量,达到同步执行的目的

1 import random 2 import time 3 from multiprocessing import Process, Semaphore 4 # from threading import Thread, Semaphore 5 6 7 def parking_lot(car, semaphore): 8 """停车场""" 9 # semaphore.acquire() 10 # print(f"{car}进入停车场,目前停车位:{semaphore.get_value()}") 11 # # 因为我们都不知道顾客会停留在里面多久,所以我们使用随机数模拟这个停留过程 12 # time.sleep(random.randrange(4, 10)) 13 # print(f"P{car}离开停车场,目前停车位:{semaphore.get_value()+1}") 14 # semaphore.release() 15 16 with semaphore: 17 print(f"{car}进入停车场,目前停车位:{semaphore.get_value()}") 18 # 因为我们都不知道顾客会停留在里面多久,所以我们使用随机数模拟这个停留过程 19 time.sleep(random.randrange(4, 10)) 20 print(f"P{car}离开停车场,目前停车位:{semaphore.get_value()+1}") 21 22 if __name__ == "__main__": 23 # 最多允许4个进程同时上锁 24 semaphore = Semaphore(4) 25 26 # 模拟10个顾客开车进来 27 for i in range(10): 28 # 顾客什么时候来的我们也不清楚,所以模拟下这个时间过程 29 time.sleep(random.randint(1, 5)) 30 p = Process(target=parking_lot, args=(f"car-{i}", semaphore)) 31 p.start()
四 事件【Event】适用进程和线程
方法 | 描述 |
---|---|
wait() | 根据Flag的值判断是否要阻塞进程,Flag为True时阻塞,Flase时不阻塞 |
set() | 将Flag的值改成True |
clear() | 将Flag的值改成False |
is_set() | 判断当前的Flag的值 |

1 import time, random 2 from multiprocessing import Process, Event 3 4 5 def traffic_light(event): 6 """红绿灯程序""" 7 while True: 8 if event.is_set(): # 判断事件中的Flag标记的值,如果是True,则亮红灯 9 print("红灯亮") 10 event.clear() # 亮完红灯以后,把Flag标记的值改为False 11 else: 12 print("绿灯亮") 13 event.set() # 亮完绿灯以后,把Flag标记的值改为True 14 time.sleep(2) 15 16 def car(i, event): 17 """车""" 18 if not event.is_set(): 19 print(f"car{i}等待红灯") 20 event.wait() 21 print(f"car{i}通过了路口。") 22 23 if __name__ == '__main__': 24 # 创建一个事件对象 25 event = Event() 26 p = Process(target=traffic_light, args=(event,)) 27 p.start() 28 29 # 模拟30辆小车通过红绿灯 30 for i in range(30): 31 # 我们不知道什么时候有车来到路口,所以随机时间来模拟这个过程 32 time.sleep(random.randrange(0, 2)) 33 p = Process(target=car, args=(i, event)) 34 p.start()
五 池【适用进程和线程】重要‼️
一个进程池或线程池,在里面放上固定数量的进程或线程,有任务来了,就拿池中的进程或线程对象来处理任务,等任务处理完毕,进程或线程并不关闭,而是将进程或线程再放回池中等待下一次任务到来。如果有很多任务需要并发执行,池中的进程或线程数量不够,任务就要等待之前的进程或线程执行任务完毕归来,拿到空闲进程或线程才能继续执行。
也就是说,池中进程或线程的数量是固定的,那么同一时间最多有固定数量的进程或线程在运行。这样不仅减轻了操作系统的调度难度,还节省了开闭进程或线程的开销,同时实现了并发效果。
5.1 实现进程池
python中提供了2个模块提供操作:
-
multiprocessing.Pool:multiprocessing.Pool创建的进程提供2种不同的运行方式:apply(同步调用),apply_async(异步调用)
-
concurrent.futures.ProcessPoolExecutor 常用!
5.1.1 基于multiprocessing.Pool实现进程池

1 import time, os, random 2 from multiprocessing import Pool 3 4 def func(n): 5 print(f"子进程{n}执行了....") 6 time.sleep(2) 7 return f"子进程{n}" 8 9 if __name__ == '__main__': 10 start_time = time.time() 11 """创建一个进程池""" 12 # n = os.cpu_count() # 本机CPU个数,我的是12,进程池容量个数自定义,默认CPU核数 13 # p = Pool(processes=n) 14 p = Pool(4) # 指定进程池中初始化时创建多少个进程在里面,默认根据操作系统的CPU逻辑数量来创建 15 """往进程池里面的进程添加要执行的任务""" 16 res_list = [] 17 # 创建20个任务 18 for i in range(20): 19 res = p.apply(func, args=(i,)) # 使用同步调用的方式,apply的返回值是任务的return返回值 20 res_list.append(res) 21 22 print(f'使用时间: {time.time() - start_time}') 23 print(f"全部任务的执行结果:{res_list}")

1 import time, os, random 2 from multiprocessing import Pool 3 4 def func(n): 5 print(f"子进程{n}执行了....") 6 time.sleep(2) 7 return f"子进程{n}" 8 9 if __name__ == '__main__': 10 start_time = time.time() 11 """创建一个进程池""" 12 # n = os.cpu_count() # 本机CPU个数,我的是12,进程池容量个数自定义, 默认CPU逻辑核数 13 # p = Pool(processes=n) 14 p = Pool() # 指定进程池中初始化时创建多少个进程在里面,默认根据操作系统的CPU逻辑数量来创建 15 """往进程池里面的进程添加要执行的任务""" 16 res_list = [] 17 # 创建20个任务 18 for i in range(20): 19 res = p.apply_async(func, args=(i,)) # 使用异步调用的方式,apply_async的返回值是任务的异步结果对象 20 res_list.append(res) # 21 22 p.close() # 关闭进程池, 不再有新的任务加入到pool中, 防止进一步的操作 23 p.join() # 必须在close调用之后执行, 执行后等待所有子进程结束,否则报错 24 25 print(f'使用时间: {time.time() - start_time}') 26 27 results = [res.get() for res in res_list] # get() 同步阻塞方法 28 print(f"全部任务的执行结果:{results}")
异步调用实例apply_async:进程池实现socketserver

1 import socket 2 from multiprocessing import Pool 3 4 def talk(conn): 5 """通信方法""" 6 while True: 7 message = conn.recv(1024) 8 print(message) 9 conn.send(message) 10 conn.close() 11 12 if __name__ =="__main__": 13 sk = socket.socket() 14 sk.bind(("127.0.0.1", 9000)) 15 sk.listen(5) 16 17 # Pool默认获取cpu_counter cpu最大逻辑核心数我的机器是12 18 p = Pool() 19 20 while True: 21 conn, addr = sk.accept() 22 p.apply_async(talk, args=(conn,)) 23 sk.close()

1 import socket 2 sk = socket.socket() 3 sk.connect( ("127.0.0.1", 9000) ) 4 5 while True: 6 content = input(">:") 7 sk.send(content.encode("utf-8")) 8 print(sk.recv(1024))
5.1.2 基于multiprocessing.futures.ProcessPoolExecutor实现进程池

1 import random, time 2 from concurrent.futures import ProcessPoolExecutor 3 4 def func(n): 5 print(f"子进程{n}开始执行...") 6 time.sleep(random.randint(1, 3)) 7 print(f"子进程{n}执行结束...") 8 return f"子进程{n}" # 任务的返回值 9 10 11 if __name__ == '__main__': 12 # 创建进程池, 13 # 可以通过processes参数指定进程池中初始化时创建多少个进程在里面, 14 # 默认根据操作系统的CPU逻辑数量来创建 15 p = ProcessPoolExecutor(max_workers=4) 16 res_list = [] 17 for i in range(20): 18 res = p.submit(func, i) # 第一个参数为任务函数名,后续参数均为任务函数的参数 19 res_list.append(res) # submit的返回值是一个异步对象,通过对象的result方法可以获取任务结果 20 21 # print([res.result() for res in res_list]) # result阻塞同步方法,用于提取任务结果,也就是func的返回值 22 23 # 关闭进程池,后续不能继续执行submit提交任务,并阻塞等待所有的提交任务全部执行完成。 24 # 相当于原来的 for p in p_list: p.join() 25 p.shutdown() #阻塞效果 26 print("主进程结束")

1 import random, time 2 from concurrent.futures import ProcessPoolExecutor 3 4 def func(n): 5 print(f"子进程{n}开始执行...") 6 time.sleep(random.randint(1, 3)) 7 print(f"子进程{n}执行结束...") 8 return f"子进程{n}" # 任务的返回值 9 10 11 if __name__ == '__main__': 12 # 创建进程池, 13 # 可以通过processes参数指定进程池中初始化时创建多少个进程在里面, 14 # 默认根据操作系统的CPU逻辑数量来创建 15 p = ProcessPoolExecutor(max_workers=4) 16 17 # res_list = [] 18 # for i in range(20): 19 # res = p.submit(func, i) # 第一个参数为任务函数名,后续参数均为任务函数的参数 20 # res_list.append(res) # submit的返回值是一个异步对象,通过对象的result方法可以获取任务结果 21 22 # print([res.result() for res in res_list]) # result阻塞同步方法,用于提取任务结果,也就是func的返回值 23 24 # 关闭进程池,后续不能继续执行submit提交任务,并阻塞等待所有的提交任务全部执行完成。 25 # 相当于原来的 for p in p_list: p.join() 26 # p.shutdown() 27 28 res_list = p.map(func, range(20)) 29 print([res for res in res_list]) 30 print("主进程结束")

1 import random, time 2 from concurrent.futures import ProcessPoolExecutor 3 4 def task(n): 5 print(f"子进程{n}开始执行...") 6 time.sleep(random.randint(1, 3)) 7 print(f"子进程{n}执行结束...") 8 return f"子进程{n}" # 任务的返回值 9 10 11 def task_callback(res): 12 print(f"对任务结果进行异步回调处理:{res.result()}") 13 14 15 if __name__ == '__main__': 16 p = ProcessPoolExecutor(2) 17 for i in range(5): 18 p.submit(task, i).add_done_callback(task_callback) 19 20 p.shutdown() 21 print("主进程结束") 22 23 # 把结果处理流程编程了同步回调处理了 24 # res_list = [] 25 # for i in range(5): 26 # res = p.submit(task, i) 27 # res_list.append(res) 28 # 29 # for res in res_list: 30 # task_callback(res) # result 同步阻塞 31 # 32 # p.shutdown() 33 # print("主进程结束")
5.2 实现线程池 常用
threading模块并没有像multiprocessing模块那样提供类似进程池的功能,所以我们要实现线程池,只能通过concurrent.futures模块提供的ThreadPoolExecutor线程池类来实现,其用法与上面的的ProcessPoolExecutor一模一样。线程池也有map方法,也有add_done_callback的结果异步回调操作。

1 import random 2 import time 3 from concurrent.futures import ThreadPoolExecutor 4 5 def func(n): 6 print(f"子线程{n}开始执行...") 7 time.sleep(random.randint(1, 5)) 8 print(f"子线程{n}执行结束...") 9 return n 10 11 if __name__ == '__main__': 12 p = ThreadPoolExecutor(4) 13 results = [] 14 for i in range(20): 15 res = p.submit(func, i) # 第一个参数为函数名,后续参数为函数的参数 16 results.append(res) 17 18 # p.shutdown() # 关闭进程池,后续不能继续执行submit提交任务,并阻塞等待所有的提交任务全部执行完成。 19 print([r.result() for r in results]) # 提取任务结果,也就是func的返回值 20 print("主线程结束") 21 # 这里也有map方法,也有add_deno_callback的回调操作
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战