一:进程理论

1.程序和进程

程序就是一堆代码文件,是指令和数据的集合,可以作为目标文件保存在磁盘中,或者作为段存放在内存地址空间中。(静态)

进程就是一个程序运行的过程,是操作系统进行资源分配和保护的基本单位(动态)

进程是资源分配的最小单位

1个程序可以对应多个进程,但是1个进程只能对应1个程序。进程和程序的关系犹如演出和剧本的关系

2.并发、并行、串行

串行:多个任务依次运行、一个进程运行完毕、在运行下一个

并发:多个任务看起来像是在同时运行,本质上还是一个个地运行

并行:多个任务真正意义上一起运行,只有多个CPU才能并行的概念

Python并发编程-进程 - 图1

3.同步-异步/阻塞 - 非阻塞

同步(慢)
发起一个请求,直到请求返回结果之后,才进行下一步操作

  1. 就行运行在一段代码,自上而下一行一行运行,只有上一行代码执行完成,才能执行下一行代码
  2. 简单来说:**同步**就是:**必须一件事一件事的做**,**等前一件事做完了,才能做下一件事**

异步(快):

  1. **发起一个请求后,不管这个请求有没有返回结果,直接可以进行下一步操作**
  2. 一般情况下,有一个回调的操作
  3. 简单来说:**异步**就是:**可以多件事情同时做**。

阻塞(慢):

  1. 遇到IO操作,CPU会被操作系统切换到其它进程。
  2. **调用结果返回之前,当前线程会被挂起**。调用线程只用在得到结果之后才会返回。
  3. 简单来说,**阻塞**就是:需要买奶茶和冰淇淋,买奶茶的时候奶茶制作过程**只能等**,然后买完奶茶再去买冰淇淋

非阻塞(快):

  1. 没有IO操作,一直运行
  2. **在不能立刻得到结果之前,改调用不会阻塞当前线程**
  3. 简单来说,**非阻塞**就是:需要买奶茶和冰淇淋,买奶茶的时候等的时间去买冰淇淋。

最佳状态:异步+非阻塞
拓展:

  1. 同步框架:FlakDjango3.0之前
  2. 异步框架:TornadoSanicFastAPI

例子:

背景:妈妈出门了,小明想要看电视,但是妈妈让小明烧水。

1.小明烧水,在一旁等待,时不时看一下水有没有烧开。 —— 同步阻塞

2.小明烧水,等待的时间偷偷去看会儿电视,看电视的时候时不时来看一下水有没有烧开。 —— 同步非阻塞

3.小明买了一个水开了之后会响的水壶来烧水,烧水时在一旁等着,不用时不时看水有木有烧开。 —— 异步阻塞

4.小明用那个会响的水壶烧水,然后去看电视,等水开了之后发出响声,再去开。 —— 异步非阻塞

4.进程运行的三种状态

打开一个应用程序,该进程会进入就绪态,获取CPU资源后,执行程序代码,进入运行态,出现了读写文件with open(…),就会进入阻塞态,CPU资源就会被操作系统分配到其他进程

运行态(Running)(非阻塞):进程已获CPU,正在执行。单处理机系统中。处于执行状态的进程只有一个;多处理机系统中,有多个处于执行状态的进程。

就绪态(Ready)(非阻塞):进程以获得除CPU外的所有必要资源,只等待CPU时的状态。一个系统会将多个处于就绪状态的进程排成一个就绪队列

阻塞态(Blocked)(阻塞):正在执行的进程由于某种原因(文件读写等IO操作)而暂时无法继续执行,便放弃处理机而处于暂停状态,即进程执行受阻。(这种状态又称等待状态或封锁状态)

就绪 → 运行

处于就绪状态的进程,当进程调度程序为之分配了处理器后,该进程便由就绪状态转变成运行状态。

运行 → 就绪

处于执行状态的进程在其执行过程中,因分配给它的一个时间片已用完而不得不让出处理机,于是进程从运行状态转变成就绪状态。

运行 → 阻塞

正在执行的进程因等待某种事件(文件读写等IO操作)发生而无法继续执行时,便从运行状态变成阻塞状态。

阻塞 → 就绪

处于阻塞状态的进程,若其等待的事件已经发生,于是进程由阻塞状态转变为就绪状态。

Python并发编程-进程 - 图2

优化程序效率的核心法则

降低IO操作(硬盘IO、网络IO)

数据获取优先级:

内存 > 本地硬盘 > 网络IO

5.提交任务的2种方式

同步调用:一个任务必须要执行完毕才能执行另一个任务

异步调用:一个任务在执行过程中,可以执行另一个任务

二:进程的使用

 1.进程的创建

Windows:CreateProcess

Linux:Fork

2.进程的终止

1.正常退出(自愿;程序执行完毕后,终止,资源被回收)

2.出错退出(自愿;python3 test.py但是test.py不存在)

3.严重错误(非自愿;执行非法指令)

4.被其他进程杀死(非自愿;被操作系统杀死taskkill /F /PID 3333

三:join方法

0.作死法

  1. import time
  2. from multiprocessing import Process
  3. def task(n):
  4. print('%s run' % n)
  5. time.sleep(10000)
  6. if __name__ == '__main__':
  7. for i in range(10000):
  8. p = Process(target=task, args=(1,))
  9. p.start()
  10. print('主进程')

1.单个进程 + 单个子进程

  1. import os
  2. import time
  3. from multiprocessing import Process
  4. def task(n):
  5. print(f'task[{os.getpid()}] is running')
  6. time.sleep(n)
  7. print(f'task[{os.getpid()}] is done')
  8. if __name__ == '__main__':
  9. # p = Process(target=task, args=(5,)) # args中为元组/列表
  10. p = Process(target=task, kwargs={'n': 5}) # kwargs中为字典
  11. p.start() # 在向发操作系统发送启动子进程的信号,属于IO操作,速度慢
  12. print(f'主进程[{os.getpid()}]')
  13. # 主进程[15188]
  14. # task[86956] is running
  15. # task[86956] is done

2.单个父进程 + 多个子进程 方式1

  1. import os
  2. import time
  3. from multiprocessing import Process
  4. def task(n, name):
  5. print(f'task[{name}] is running [{os.getpid()}]')
  6. time.sleep(n)
  7. print(f'task[{name}] is done [{os.getpid()}]')
  8. if __name__ == '__main__':
  9. p1 = Process(target=task, args=(2, 'p1'))
  10. p2 = Process(target=task, args=(2, 'p2'))
  11. p3 = Process(target=task, args=(2, 'p3'))
  12. p1.start() # 在向发操作系统发送启动子进程的信号,属于IO操作,速度慢
  13. p2.start() # 在向发操作系统发送启动子进程的信号,属于IO操作,速度慢
  14. p3.start() # 在向发操作系统发送启动子进程的信号,属于IO操作,速度慢
  15. print(f'主进程[{os.getpid()}]')
  16. # 主进程[79800]
  17. # task[p1] is running [17320]
  18. # task[p3] is running [66276]
  19. # task[p2] is running [36784]
  20. # task[p1] is done [17320]
  21. # task[p3] is done [66276]
  22. # task[p2] is done [36784]

3.单个父进程 + 多个子进程 方式2

  1. import os
  2. import time
  3. from multiprocessing import Process
  4. class Myprocess(Process):
  5. def __init__(self, tag):
  6. super().__init__()
  7. self.tag = tag
  8. def run(self) -> None: # 表示run函数的返回值为空,无返回值
  9. print(f'task[{self.tag}] is running [{os.getpid()}]')
  10. time.sleep(3)
  11. print(f'task[{self.tag}] is done [{os.getpid()}]')
  12. if __name__ == '__main__':
  13. p1 = Myprocess('进程1')
  14. p2 = Myprocess('进程2')
  15. p3 = Myprocess('进程3')
  16. p1.start() # 相当于:p1.run,但是p1.run是同步的,所以这里用p1.start
  17. p2.start() # p2.run
  18. p3.start() # p3.run
  19. print(f'主进程[{os.getpid()}]')

4.进程之间的数据是隔离的

  1. 因为:**开启1个进程,就是开启了1Python解释器。**
  2. **Windows**下要开进程,需要在**main函数**下
  1. import os
  2. import time
  3. from multiprocessing import Process
  4. age = 18
  5. def task(n, name):
  6. global age # 局部修改全局
  7. age = 99
  8. print(f'task[{name}] is running [{os.getpid()}]')
  9. time.sleep(n)
  10. print(f'task[{name}] is done [{os.getpid()}]')
  11. print(f'子进程的age:{age}')
  12. if __name__ == '__main__':
  13. p1 = Process(target=task, args=(1, 'p1'))
  14. p1.start()
  15. print(f'主进程[{os.getpid()}]')
  16. print(f'主进程的age:{age}')
  17. # 主进程[2680]
  18. # 主进程的age:18
  19. # task[p1] is running [45116]
  20. # task[p1] is done [45116]
  21. # 子进程的age:99

5.join - 让子进程的开启者 等p1开启并运行完毕后 再运行

  1. import os
  2. import time
  3. from multiprocessing import Process
  4. def task(n, name):
  5. print(f'task[{name}] is running [{os.getpid()}]')
  6. time.sleep(n)
  7. print(f'task[{name}] is done [{os.getpid()}]')
  8. if __name__ == '__main__':
  9. p1 = Process(target=task, args=(2, 'p1'))
  10. p1.start() # 在向发操作系统发送启动子进程的信号,属于IO操作,速度慢
  11. p1.join() # join让子进程执行完成
  12. print(f'主进程[{os.getpid()}]')
  13. # task[p1] is running [47164]
  14. # task[p1] is done [47164]
  15. # 主进程[44356]

不用join - 不等待 耗时0.几秒

  1. import os
  2. import time
  3. from multiprocessing import Process
  4. def task(n, name):
  5. print(f'task[{name}] is running [{os.getpid()}]')
  6. time.sleep(n)
  7. print(f'task[{name}] is done [{os.getpid()}]')
  8. if __name__ == '__main__':
  9. ctime = time.time()
  10. p1 = Process(target=task, args=(1, 'p1'))
  11. p2 = Process(target=task, args=(2, 'p1'))
  12. p1.start()
  13. p2.start()
  14. # p1.join()
  15. # p2.join()
  16. print(f'主进程[{os.getpid()}]')
  17. print(time.time() - ctime)
  18. # 不用join
  19. # 主进程[59116]
  20. # 0.020940065383911133
  21. # task[p1] is running [89420]
  22. # task[p1] is running [91328]
  23. # task[p1] is done [89420]
  24. # task[p1] is done [91328]

只用p1.join - 等待p1执行完毕再执行主进程

  1. import os
  2. import time
  3. from multiprocessing import Process
  4. def task(n, name):
  5. print(f'task[{name}] is running [{os.getpid()}]')
  6. time.sleep(n)
  7. print(f'task[{name}] is done [{os.getpid()}]')
  8. if __name__ == '__main__':
  9. ctime = time.time()
  10. p1 = Process(target=task, args=(1, 'p1'))
  11. p2 = Process(target=task, args=(2, 'p2'))
  12. p1.start()
  13. p2.start()
  14. p1.join()
  15. # p2.join()
  16. print(f'主进程[{os.getpid()}]')
  17. print(time.time() - ctime)
  18. # 只用p1.join
  19. # task[p1] is running [61892]
  20. # task[p2] is running [38660]
  21. # task[p1] is done [61892]
  22. # 主进程[47172]
  23. # 1.1537489891052246
  24. # task[p2] is done [38660]

只用p2.join - 等待p2执行完毕再执行主进程

  1. import os
  2. import time
  3. from multiprocessing import Process
  4. def task(n, name):
  5. print(f'task[{name}] is running [{os.getpid()}]')
  6. time.sleep(n)
  7. print(f'task[{name}] is done [{os.getpid()}]')
  8. if __name__ == '__main__':
  9. ctime = time.time()
  10. p1 = Process(target=task, args=(1, 'p1'))
  11. p2 = Process(target=task, args=(2, 'p2'))
  12. p1.start()
  13. p2.start()
  14. # p1.join()
  15. p2.join()
  16. print(f'主进程[{os.getpid()}]')
  17. print(time.time() - ctime)
  18. # 只用p2.join
  19. # task[p1] is running [56056]
  20. # task[p2] is running [47384]
  21. # task[p1] is done [56056]
  22. # task[p2] is done [47384]
  23. # 主进程[3424]
  24. # 2.159543514251709

同时p1.join和p2.join - 时间取决于最长的那个

  1. import os
  2. import time
  3. from multiprocessing import Process
  4. def task(n, name):
  5. print(f'task[{name}] is running [{os.getpid()}]')
  6. time.sleep(n)
  7. print(f'task[{name}] is done [{os.getpid()}]')
  8. if __name__ == '__main__':
  9. ctime = time.time()
  10. p1 = Process(target=task, args=(1, 'p1'))
  11. p2 = Process(target=task, args=(2, 'p2'))
  12. p1.start()
  13. p2.start()
  14. p1.join()
  15. p2.join()
  16. print(f'主进程[{os.getpid()}]')
  17. print(time.time() - ctime)
  18. # p1.join + p2.join
  19. # task[p1] is running [56056]
  20. # task[p2] is running [47384]
  21. # task[p1] is done [56056]
  22. # task[p2] is done [47384]
  23. # 主进程[3424]
  24. # 2.159543514251709

6.join - 运行多个子进程 - 串行

  1. import os
  2. import time
  3. from multiprocessing import Process
  4. def task(n, name):
  5. print(f'task[{name}] is running [{os.getpid()}]')
  6. time.sleep(n)
  7. print(f'task[{name}] is done [{os.getpid()}]')
  8. if __name__ == '__main__':
  9. p1 = Process(target=task, args=(2, 'p1'))
  10. p2 = Process(target=task, args=(3, 'p2'))
  11. p3 = Process(target=task, args=(4, 'p3'))
  12. start = time.time()
  13. p1.start()
  14. p1.join()
  15. p2.start()
  16. p2.join()
  17. p3.start()
  18. p3.join()
  19. print(f'主进程[{os.getpid()}]')
  20. print('耗时:', time.time() - start)
  21. # task[p1] is running [72452]
  22. # task[p1] is done [72452]
  23. # task[p2] is running [31540]
  24. # task[p2] is done [31540]
  25. # task[p3] is running [62568]
  26. # task[p3] is done [62568]
  27. # 主进程[11548]
  28. # 耗时: 9.440884351730347

7.join - 运行多个子进程 - 并发

  1. import os
  2. import time
  3. from multiprocessing import Process
  4. def task(n, name):
  5. print(f'task[{name}] is running [{os.getpid()}]')
  6. time.sleep(n)
  7. print(f'task[{name}] is done [{os.getpid()}]')
  8. if __name__ == '__main__':
  9. p1 = Process(target=task, args=(2, 'p1'))
  10. p2 = Process(target=task, args=(3, 'p2'))
  11. p3 = Process(target=task, args=(4, 'p3'))
  12. start = time.time()
  13. p1.start()
  14. p2.start()
  15. p3.start()
  16. p1.join() # 此时,p1、p2、p3的join顺序已经不重要了
  17. p2.join()
  18. p3.join()
  19. print(f'主进程[{os.getpid()}]')
  20. print('耗时:', time.time() - start)
  21. # task[p2] is running [47248]
  22. # task[p3] is running [46152]
  23. # task[p1] is running [43612]
  24. # task[p1] is done [43612]
  25. # task[p2] is done [47248]
  26. # task[p3] is done [46152]
  27. # 主进程[3776]
  28. # 耗时: 4.173335313796997

四:进程调度

想要多个进程交替运行,就需要操作系统来调度CPU的时间片

1.先来先服务(FCFS)(First Come First Serve)

先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。

FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。

由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)。

2.短作业优先

短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度。

但其对长作业不利;不能保证紧迫性作业(进程)被及时处理;作业的长短只是被估算出来的

3.时间片轮转

时间片轮转(Round Robin,RR)法的基本思路是让每个进程就绪队列中的等待时间享受服务的时间成比例。

在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,几十毫秒至几百毫秒。

如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度。

同时,进程调度程序又去调度当前就绪队列中的第一个进程

4.多级反馈队列

前面介绍各种用作进程调度算法有一定的局限性

如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式调度算法都将无法使用。

多级反馈队列调度算法不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法

五:僵尸进程 与 孤儿进程

1.僵尸进程

进程结束之后,还有数据没回收。

子进程结束后,父进程未将子进程资源回收,该子进程就编程了僵尸进程。

2.孤儿进程

主进程结束了,子进程还没结束。

父进程结束了,子进程未结束,该子进程变成了孤儿进程。

该子进程就会被专门的进程接管。

六:进程对象 及 其他方法

1.每一个进程 都会有自己的ID号,这个ID叫做PID

每个程序都可以有多个进程

Python并发编程-进程 - 图3

2.Windows中查看进程

  1. tasklist |findstr python

3.Linux、Mac中查看进程

  1. ps aux |grep

4.进程对象:

  1. ==p = Process(target = task, )==或者是在进程内部:current_process()

5.查看当前进程的ID:

p.pid、os.getpid()或者current_process().pid

6.获取父进程ID号:

os.getppid(),子进程中获取父进程ID号,等于父进程的ID号

7.查看当前进程是否存活:

p.is_alive()或者current_process().is_alive()

8.主进程的父进程

是pycharm,pycharm的父进程是系统(centos6中是init,centos7及以后是system)

9.关闭进程:

p.terminate(),只能在主进程中关闭子进程

七:守护进程

主进程结束之后,守护进程也随它而去,不会变成孤儿进程

进程名.daemon = True一定要加在进程名.start()之前

  1. import os
  2. import time
  3. from multiprocessing import Process
  4. def task():
  5. print(f'子进程开始,ID为:{os.getpid()}')
  6. time.sleep(100)
  7. print('子进程结束')
  8. if __name__ == '__main__':
  9. p = Process(target=task,)
  10. # 守护进程:主进程一旦结束,子进程也结束
  11. p.daemon = True # 一定要加在启动之前,将p进程设置为主进程的守护进程
  12. p.start()
  13. time.sleep(1)
  14. print('主进程')
  15. print(f'主进程ID:{os.getpid()}')
  16. # 子进程开始,ID为:17324
  17. # 主进程
  18. # 主进程ID:66772

问题:

1.主进程的父进程是谁?

pycharm

2.主进程开了很多子进程,每个都需要设置守护进程吗?

根据需求来设置,如果需要,就设置:p.deamon = True

八:互斥锁

多个进程操作同一份数据的时候,会出现数据错乱的问题

针对上述问题,解决方式就是加锁处理:将并行变成串行,牺牲效率但是保证了数据的安全

json字符串转换网站:json.cn

多个人同时抢锁,只有一个人可以拿到,必须释放,其他人才能拿到。

例子:

10个人抢票,票只有1张,10个人可以同时查票,但是能买到票的只有1个,这个人买到票之后,其他的人就没票可以买了

ticket.txt

  1. {"ticket_count": 1}

mylock.py

  1. import json
  2. import time
  3. import random
  4. from multiprocessing import Process, Lock
  5. # 查票
  6. def search():
  7. # 打开文件,读出ticket_couont
  8. with open('ticket.txt', mode='r', encoding='UTF-8') as f:
  9. dic = json.load(f)
  10. # print(dic['ticket'])
  11. print(f'剩余票量:{dic.get("ticket_count")}')
  12. # 买票
  13. def buy():
  14. with open('ticket.txt', mode='r', encoding='UTF-8') as f:
  15. dic = json.load(f)
  16. time.sleep(random.randint(1, 3)) # 模拟网络延迟
  17. if dic.get('ticket_count') > 0: # # 余票大于0,能够买票
  18. dic['ticket_count'] -= 1
  19. # 保存到文件中
  20. with open('ticket.txt', mode='w', encoding='UTF-8') as f:
  21. json.dump(dic, f)
  22. print('买票成功!')
  23. else:
  24. print('余票不足,买票失败!')
  25. # 写一个函数,先查票,再买票
  26. def task(mutex):
  27. search()
  28. # 买票过程 需要加锁
  29. # 买前加锁
  30. # mutex.acquire()
  31. # buy() # 10个进程,编程了串行
  32. # # 买后释放锁
  33. # mutex.release()
  34. with mutex:
  35. buy()
  36. if __name__ == '__main__':
  37. # 锁的创建 应该设置在主进程中
  38. mutex = Lock() # 创建一把锁
  39. # 模拟10个人查票+买票(开10个进程)
  40. for i in range(10):
  41. t = Process(target=task, args=(mutex,))
  42. t.start()

了解:

分布式锁、悲观锁、乐观锁

九:队列介绍

队列可以并发的派多个线程,对排列的线程处理,并且每个需要处理线程只需要将请求的数据放入队列容器的内存中,线程不需要等待,当排列完毕处理完数据后,线程在准时来取数据即可。

请求数据的线程只与这个队列容器存在关系,处理数据的线程down掉不会影响到请求数据的线程,队列会派给其他线程处理这分数据,它实现了解耦提高效率

队列内会有一个有顺序的容器列表与这个容器是有区别的,列表中数据虽然是排列的,但数据被取走后还会保留,而队列中这个容器的数据被取后将不会保留

当必须在多个线程之间安全地交换信息时,队列在线程编程中特别有用

1.作用

解耦:使程序直接实现松耦合,修改一个函数,不会有串联关系。

提高处理效率:FIFO = 先进先出,LIFO = 后入先出。

2.队列的各个参数

  1. from multiprocessing.queues import Queue
  2. Queue.Queue(maxsize=0) # FIFO, 如果maxsize小于1就表示队列长度无限
  3. Queue.LifoQueue(maxsize=0) # LIFO, 如果maxsize小于1就表示队列长度无限
  4. Queue.qsize() # 返回队列的大小
  5. Queue.empty() # 如果队列为空,返回True,反之False
  6. Queue.full() # 如果队列满了,返回True,反之False
  7. Queue.get([block[, timeout]]) # 读队列,取出数据 ,没有数据将会等待timeout等待时间
  8. Queue.put(item, [block[, timeout]]) # 写队列,放入数据,timeout等待时间
  9. Queue.queue.clear() # 清空队列
  10. class queue.PriorityQueue(maxsize=0) # 存储数据时可设置优先级的队列,优先级设置数越小等级越高
  11. Queue.get(timeout=1) # 如果1秒后没取到数据就退出
  12. Queue.get_nowait() # 取数据,如果没数据抛queue.Empty异常
  13. Queue.task_done() # 后续调用告诉队列,任务的处理是完整的。

3.实例

  1. from multiprocessing import Queue
  2. # 实例化得到要给的对象
  3. q = Queue(5) # 默认很大,可以放很多,写了个5,只能放5个
  4. # 往管道中放值
  5. q.put(1)
  6. q.put('xxq')
  7. q.put(18)
  8. q.put(18)
  9. q.put(18)
  10. # 从管道中取值
  11. # print(q.get())
  12. # print(q.get())
  13. # print(q.get(timeout=0.1)) # 等0.1s还没有值,就结束
  14. # print(q.get_nowait()) # 不等了,有就是有,没有就是没有
  15. print(q.empty()) # 看一下队列是不是空的 False
  16. print(q.full()) # 看一下队列是不是满的 True

4.总结

  1. q = Queue(队列大小)
  2. # 放值
  3. q.put('xxq')
  4. q.put_nowait(asd) # 队列满了,放不进去就不放了,报错
  5. # 取值
  6. q.get() # 从队列头部取出一个值
  7. q.get_nowait() # 从队列头部取值,没有就报错
  8. # 队列是否为空,是否满
  9. print(q.empty()) # 看一下队列是不是空的
  10. print(q.full()) # 看一下队列是不是满的

5.了解

消息队列Message Queue):RabbitMQ、Kafka

十:IPC机制

1.简介

IPCInter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程

2.实现进程间通信的2种方式

管道:pipe

队列:queue(其实就是pipe+lock)

注意:两者实际上都是内存空间,不要往里面放入大数据,只能放数据量较小的消息

3.IPC所解决的问题

1.当多个任务并发的去修改共享数据,就可能会造成数据错乱,我们通过加互斥锁使多个任务对共享数据的操作由并发变为“串行”,从而保证了共享数据的安全,而当出现需要修改多个共享数据的需求时,我们就得再次加锁处理

——>IPC帮我们解决了需要自己加锁的问题

2.进程间的内存空间彼此隔离的,如何完成通信(数据交互),就需要寻求一种共享的东西硬盘是共享的,但是读取硬盘的速度慢

——>IPC实现了一种内存空间上的共享(两个进程之间通过队列交流)

4.实例

  1. from multiprocessing import Process, Queue
  2. import os
  3. def task1(q):
  4. print(f'I\'m task1,my id is {os.getpid()}')
  5. q.put('Hello World')
  6. def task2(q):
  7. # res=q.get()
  8. print(f'I\'m task2,my id is {os.getpid()}')
  9. if __name__ == '__main__':
  10. q = Queue(5)
  11. t1 = Process(target=task1, args=(q,))
  12. t1.start()
  13. t2 = Process(target=task2, args=(q,))
  14. t2.start()
  15. print(f'I\'m Main Process,I\'ll print {q.get()}')
  16. # I'm task2,my id is 49516
  17. # I'm task1,my id is 43284
  18. # I'm Main Process,I'll print Hello World

十一:生产者消费者模型

在并发编程中 使用生产者和消费者模式能够解决绝大多数并发问题

该模式通过平衡生产线程消费线程的工作能力来提高程序的整体处理数据的速度

1.什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区平衡生产者和消费者的处理能力

2.为什么要使用生产者和消费者模式?

线程世界里,生产者就是生产数据的线程消费者就是消费数据的线程

多线程开发当中,如果生产者处理速度,而消费者处理速度,那么生产者就必须等待消费者处理完,才能继续生产数据

同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者

为了解决这个问题,就在生产者和消费者之间通过队列增加缓冲避免了生产者和消费者 之间的交互。于是引入了生产者和消费者模式

Python并发编程-进程 - 图4

3.用队列实现生产者消费者模型

入门版 单生产者 单消费者 - 有BUG

  1. from multiprocessing import Process, Queue
  2. import random
  3. import time
  4. def producer(p_name, p_food, q):
  5. for i in range(10):
  6. data = f'[{p_name}]制造了[{p_food}]'
  7. time.sleep(random.randint(1, 2)) # 模拟制造食物延迟
  8. print(data)
  9. q.put(p_food)
  10. def consumer(c_name, q):
  11. while True:
  12. c_food = q.get()
  13. time.sleep(random.randint(1, 2)) # 模拟消费食物延迟
  14. print(f'[{c_name}]消费了[{c_food}]')
  15. if __name__ == '__main__':
  16. q = Queue
  17. p1 = Process(target=producer, args=('xxq', 'Noodles', q))
  18. p1.start()
  19. c1 = Process(target=consumer, args=('darker', q))
  20. c1.start()
  21. # BUG:如果消费者消费完了队列中的食物,队列食物为空,消费者就会停滞

基础版 单生产者 单消费者 - 有BUG

  1. from multiprocessing import Process, Queue
  2. import random
  3. import time
  4. def producer(p_name, p_food, q):
  5. for i in range(10):
  6. data = f'[{p_name}]制造了[{p_food}]'
  7. time.sleep(random.randint(1, 2)) # 模拟制造食物延迟
  8. print(data)
  9. q.put(p_food)
  10. q.put(None)
  11. def consumer(c_name, q):
  12. while True:
  13. c_food = q.get()
  14. if c_food is None:
  15. return
  16. time.sleep(random.randint(1, 2)) # 模拟消费食物延迟
  17. print(f'[{c_name}]消费了[{c_food}]')
  18. if __name__ == '__main__':
  19. q = Queue()
  20. p1 = Process(target=producer, args=('xxq', 'Noodles', q))
  21. p1.start()
  22. c1 = Process(target=consumer, args=('darker', q))
  23. c1.start()

进阶版 单生产者 多消费者

  1. from multiprocessing import Process, Queue
  2. import random
  3. import time
  4. def producer(p_name, p_food, q):
  5. for i in range(10):
  6. data = f'[{p_name}]制造了[{p_food}]'
  7. time.sleep(random.randint(1, 2)) # 模拟制造食物延迟
  8. print(data)
  9. q.put(p_food)
  10. def consumer(c_name, q):
  11. while True:
  12. c_food = q.get()
  13. if c_food is None:
  14. return
  15. time.sleep(random.randint(1, 2)) # 模拟消费食物延迟
  16. print(f'[{c_name}]消费了[{c_food}]')
  17. if __name__ == '__main__':
  18. q = Queue()
  19. p1 = Process(target=producer, args=('xxq', 'Noodles', q))
  20. p1.start()
  21. c1 = Process(target=consumer, args=('darker', q))
  22. c1.start()
  23. c2 = Process(target=consumer, args=('ben', q))
  24. c2.start()
  25. # 生产者生产完毕,放2个None
  26. p1.join() # p1进程执行完成,再放None
  27. q.put(None)
  28. q.put(None)

高阶版 多生产者 多消费者

  1. from multiprocessing import Process, Queue
  2. import random
  3. import time
  4. def producer(p_name, p_food, q):
  5. for i in range(10):
  6. data = f'[{p_name}]制造了[{p_food}]'
  7. time.sleep(random.randint(1, 2)) # 模拟制造食物延迟
  8. print(data)
  9. q.put(p_food)
  10. def consumer(c_name, q):
  11. while True:
  12. c_food = q.get()
  13. if c_food is None:
  14. return
  15. time.sleep(random.randint(1, 2)) # 模拟消费食物延迟
  16. print(f'[{c_name}]消费了[{c_food}]')
  17. if __name__ == '__main__':
  18. q = Queue()
  19. p1 = Process(target=producer, args=('xxq', 'Noodles', q))
  20. p1.start()
  21. p2 = Process(target=producer, args=('zsy', 'Pizza', q))
  22. p2.start()
  23. c1 = Process(target=consumer, args=('darker', q))
  24. c1.start()
  25. c2 = Process(target=consumer, args=('ben', q))
  26. c2.start()
  27. c3 = Process(target=consumer, args=('Alen', q))
  28. c3.start()
  29. # 生产者生产完毕,放2个None
  30. p1.join() # p1进程执行完成,再放None
  31. p2.join() # p2进程执行完成,再放None
  32. q.put(None)
  33. q.put(None)
  34. q.put(None)

终极版 多生产者 多消费者

  1. """
  2. 生产者:生产/制造东西的
  3. 消费者:消费/处理东西的
  4. 该模型除了上述两个之外还需要一个媒介
  5. 例子:自助餐
  6. 厨师(生产者)制作菜品之后,会将菜品放到餐台(消息队列)
  7. 顾客(消费者)想要吃,可以直接到餐台(消息队列)去拿菜品
  8. 在这个过程中,厨师(生产者)和顾客(消费者)并无直接联系
  9. 而是以餐台(消息队列)为媒介的
  10. 生产者(厨师) + 消息队列(餐台) + 消费者(顾客)
  11. """
  12. from multiprocessing import JoinableQueue, Process
  13. import random
  14. import time
  15. def producer(p_name, p_food, q):
  16. for i in range(10):
  17. print(f'{p_name} 生产了 {p_food}')
  18. # 模拟网络延迟
  19. time.sleep(random.randint(1, 2))
  20. # 将数据加入队列
  21. q.put(p_food)
  22. def consumer(c_name, q):
  23. while True:
  24. food = q.get() # 假如没有数据,程序就会在此处进入堵塞态
  25. time.sleep(random.randint(1, 2)) # 模拟消费的延迟
  26. print(f'{c_name} 消费了 {food}')
  27. q.task_done() # 告诉队列你已经从里面取出了一个数据并且处理完毕了
  28. if __name__ == '__main__':
  29. q = JoinableQueue()
  30. p1 = Process(target=producer, args=('Cook1', 'Beef', q))
  31. p2 = Process(target=producer, args=('Cook2', 'Cake', q))
  32. c1 = Process(target=consumer, args=('Alan', q))
  33. c2 = Process(target=consumer, args=('Ben', q))
  34. c3 = Process(target=consumer, args=('Cindy', q))
  35. p1.start()
  36. p2.start()
  37. c1.daemon = True # 将消费者设置成守护进程
  38. c2.daemon = True
  39. c3.daemon = True
  40. c1.start()
  41. c2.start()
  42. c3.start()
  43. p1.join()
  44. p2.join()
  45. # 等待生产者生产完毕之后 往队列中添加特定的结束符号
  46. # q.put(None) # 有多少消费者就往队列中添加多少None,新添加的None必定在队列的末尾
  47. q.join() # 等待队列中所有的数据被取完再执行往下执行代码
  48. """
  49. JoinableQueue 每当你往该队列中存入数据的时候 内部会有一个计数器+1
  50. 没当你调用task_done的时候 计数器-1
  51. q.join() 当计数器为0的时候 才往后运行
  52. """
  53. # 只要q.join执行完毕 说明消费者已经处理完数据了 消费者就没有存在的必要了

十二:进程池

  1. from concurrent.futures import ProcessPoolExecutor
  2. pool = ProcessPoolExecutor(2)
  3. pool.submit(get_pages, url).add_done_callback(call_back)