协程目的:
1、单线程下实现并发:协程
解决方案:一个任务运行的过程中把它打断(保存此时的状态)切换到另一个任务中,运行一段时间再切换回来,反复来回切换
注:并发指的是多个任务看起来是同时运行的
并发实现的本质:切换+保存状态
并发、并行、串行:
并发:看起来是同时运行,切换+保存状态
并行:真正意义上的同时运行,只有在多cpu情况下才实现并行,4个cpu能够并行4个任务
串行:一个任务完完整整地执行完毕才运行下一个任务
例如以下程序中生产者和消费者数据的来回切换:(应用程序自己控制切换及保存状态,速度更快)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import time def consumer(): '''任务1:接收数据,处理数据''' while True: x=yield def producer(): '''任务2:生产数据''' g=consumer() next(g) for i in range(10000000): g.send(i) start=time.time() #基于yield保存状态,实现两个任务直接来回切换,即并发的效果 #PS:如果每个任务中都加上打印,那么明显地看到两个任务的打印是你一次我一次,即并发执行的. producer() #1.0202116966247559 stop=time.time() print(stop-start)
但如果一个程序中是纯计算活的话,来回切换意义不大,如下:
# 纯计算的任务串行执行 import time def task1(): res=1 for i in range(1000000): res+=i def task2(): res=1 for i in range(1000000): res*=i start=time.time() task1() task2() stop=time.time() print(stop-start) # 纯计算的任务并发执行 import time def task1(): res=1 for i in range(1000000): res+=i yield #切换到task2 def task2(): g=task1() res=1 for i in range(1000000): res*=i next(g) #切换到task1 start=time.time() #基于yield保存状态,实现两个任务直接来回切换,即并发的效果 #PS:如果每个任务中都加上打印,那么明显地看到两个任务的打印是你一次我一次,即并发执行的. task2() stop=time.time() print(stop-start)
打印结果:
0.22888588905334473
0.42775511741638184
可以看出做纯计算任务并发执行的时间是串行执行的2倍,速度慢了
所以单线程下实现并发是为了提高效率,检测多个任务的IO行为,遇到IO再切到自己单线程下的另外一个任务去执行,这样就把单线程下的IO问题给尽可能多的缩减下来(相当于‘欺骗了’操作系统,在操作系统看来你一直处于工作状态,操作系统才不会把cpu拿走),这样单线程就尽可能多的处于计算过程,也就是处于就绪态。
强调:
#1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
#2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
协程优点:
#1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级 #2. 单线程内就可以实现并发的效果,最大限度地利用cpu
缺点:
#1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程 #2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程