一 生产者与消费者模型

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

 

示例一

 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}
基于manager和lock实现进程间的数据共享并保证数据安全

 

三 信号量【Semaphore】适用进程和线程

信号量不仅在进程模块multiprocessing中存在,而且threading模块中也有,作用一样,只是针对的对象不一样

递归锁【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】适用进程和线程

多进程模块multiprocessing与多线程threading模块提供了事件(Event )可以用来实现进程间/线程间的同步通信

运行机制是通过定义了一个多个进程共享的全局标记Flag,如果Flag值为 False,当程序执行event.wait()方法时就会阻塞,如果Flag值为True时,程序执行event.wait()方法时不会阻塞继续执行。


方法
描述
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()
基于事件event红路灯运作程序

 

五 池【适用进程和线程】重要‼️

 

 一个进程池或线程池,在里面放上固定数量的进程或线程,有任务来了,就拿池中的进程或线程对象来处理任务,等任务处理完毕,进程或线程并不关闭,而是将进程或线程再放回池中等待下一次任务到来。如果有很多任务需要并发执行,池中的进程或线程数量不够,任务就要等待之前的进程或线程执行任务完毕归来,拿到空闲进程或线程才能继续执行。

也就是说,池中进程或线程的数量是固定的,那么同一时间最多有固定数量的进程或线程在运行。这样不仅减轻了操作系统的调度难度,还节省了开闭进程或线程的开销,同时实现了并发效果。 

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}")
同步调用进程池apply

 

 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

 

异步调用实例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()
server.py

 

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))
client.py

 

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("主进程结束")
基于concurrent.futures实现进程池

 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("主进程结束")
map方法优化-基于concurrent.futures实现进程池

 

 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("主进程结束")
add_done_callback方法-针对进程任务结果进行异步回调处理

 

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的回调操作
线程池的实现

 

posted on 2022-05-24 22:28  大明花花  阅读(68)  评论(0编辑  收藏  举报