队列
队列 : 先进先出、数据进程安全
队列实现方式: 管道 + 锁
生产者消费者模型 : 解决数据供需不平衡
管道
双向通信 数据进程不安全
EOFError:
管道是由操作系统进行引用计数的,
必须在所有进程中关闭管道后才能生成EOFError异常
数据共享(不常用)
Manager
list dict 数据进程不安全的
进程池
存放进程的容器
在进程创建之初,创建固定个数的进程
会被多个任务循环利用
节省了进程创建和销毁的时间开销
降低了操作系统调度进程的压力
信号量和进程池的区别
信号量 n个任务开启n个进程,
但同一时间只能有固定个数的进程在执行
进程在等待被执行
进程池 n个任务开启固定个数的进程
因此同一时间只能有固定个数的进程在执行
任务在等待被执行
队列是内置锁的,所以别的应用调用它,是安全的。
凡是涉及到手动加锁的,都是不安全的。常用的一般都是消息中间件
没有返回值的情况
close和join成对使用
import time
from multiprocessing import Pool
def wahaha(i):
time.sleep(1)
print('*'*i)
if __name__ == '__main__':
p = Pool(5) #建议的数量是CPU核数+1
for i in range(5):
p.apply_async(func=wahaha,args=(i,))
p.close() # 不能再提交新的任务
p.join() # 等待池中的任务都执行完
执行输出:
*
**
***
****
有返回值的情况
import time
from multiprocessing import Pool
def wahaha(i):
time.sleep(1)
return '*'*i
if __name__ == '__main__':
p = Pool(5) #建议的数量是CPU核数+1
res_1 = []
for i in range(5):
res = p.apply_async(func=wahaha,args=(i,))
res_1.append(res)
for i in res_1:print(i.get())
执行输出:
*
**
***
****
总结:
主进程默认等待子进程结束 —— 守护进程
普通的进程 : 根据你调用的函数执行结束它就结束了
进程池里的进程
没有返回值:
在提交任务之后:
p.close() 不能再提交新的任务
p.join() 等待池中的任务都执行完
有返回值的时候:
在提交任务之后:
for i in res_l:print(i.get())
第二种写法:使用map
它不能获取返回值,参数必须是一个可迭代对象
import time
from multiprocessing import Pool
def wahaha(i):
time.sleep(1)
print('*'*i)
if __name__ == '__main__':
p = Pool(5) #建议的数量是CPU核数+1
p.map(func=wahaha,iterable=range(5)) # iterable接收一个可迭代对象
执行输出:
**
*
***
****
如果不需要返回值得,使用map即可
回调函数
回调函数在什么时候执行
子进程的任务执行完毕之后立即触发
回调函数的参数
子进程的返回值
回调函数是由谁执行的
主进程执行的
在哪儿用?
爬虫 :
如果要爬取多个格式相同的网页
真正影响程序效率的是网络的延迟
计算 分析 处理网页的时间是很快的
如果主进程要处理很久,那么就不适合用回调函数
当你不确定,到底有没有回调函数快?测试一下就知道了。
先不用回调函数,打印执行时间
再用回调函数,打印执行时间,对比一下,就知道了。
操作系统线程理论
线程概念的引入背景
进程
之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
有了进程为什么要有线程
进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:
-
进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
-
进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
如果这两个缺点理解比较困难的话,举个现实的例子也许你就清楚了:如果把我们上课的过程看成一个进程的话,那么我们要做的是耳朵听老师讲课,手上还要记笔记,脑子还要思考问题,这样才能高效的完成听课的任务。而如果只提供进程这个机制的话,上面这三件事将不能同时执行,同一时间只能做一件事,听的时候就不能记笔记,也不能用脑子思考,这是其一;如果老师在黑板上写演算过程,我们开始记笔记,而老师突然有一步推不下去了,阻塞住了,他在那边思考着,而我们呢,也不能干其他事,即使你想趁此时思考一下刚才没听懂的一个问题都不行,这是其二。
现在你应该明白了进程的缺陷了,而解决的办法很简单,我们完全可以让听、写、思三个独立的过程,并行起来,这样很明显可以提高听课的效率。而实际的操作系统中,也同样引入了这种类似的机制——线程。
线程的出现
操作系统是管理进程的,每个进程是资源隔离的。在一个操作系统中,同一时间,可以有多个任务。
多个任务之间的内存必须隔离开。比如使用qq的时候还能使用微信。并发要求日益增加,比如聊天的时候,可以开多个窗口,还可以看电影,听歌...
开启一个子进程的开销,是很大的。操作系统在进程之间的切换,时间开销也很大。
进程之间的通信:
数据共享 : 时间开销
如果多个子进程之间的数据共享量过多的时候,
就不应该将这些数据隔离开
一个进程 —— 实现不了并发
你既不希望数据隔离,还要实现并发的效果。那么就应该使用线程
线程是轻量级的进程
线程的创建和销毁所需要的时间开销都非常小
线程直接使用进程的内存
线程不能独立存在,要依赖于进程
进程和线程的关系
print(123)
执行输出:123
真正执行代码的是线程
程序启动是一个进程。
使用线程执行代码,接受CPU调度。
线程的时间开销小于进程时间开销
线程的特点
TCB包括以下信息: (1)线程状态。 (2)当线程不运行时,被保存的现场资源。 (3)一组执行堆栈。 (4)存放每个线程的局部变量主存区。 (5)访问同一个进程中的主存和其它资源。 用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。