在操做系统中进程是资源分配的最小单位,线程是CPU调度的最小单位。python
并发的本质:切换+保存状态。程序员
cpu正在运行一个任务,会在两种状况下切走去执行其余的任务(切换由操做系统强制控制),一种状况是该任务发生了阻塞,另一种状况是该任务计算的时间过长。并发
在介绍进程理论时,说起进程的三种执行状态,而线程才是执行单位,因此也能够将上图理解为线程的三种状态。函数
其中并发并不能提高效率,只是为了让cpu可以雨露均沾,实现看起来全部任务都被“同时”执行的效果,若是多个任务都是纯计算的,这种切换反而会下降效率。spa
协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。操作系统
一句话说明什么是协程:协程是一种用户态的轻量级线程,即协程是由用户程序本身控制调度的,单线程下实现并发。线程
须要强调的是:code
对比操做系统控制线程的切换,用户在单线程内控制协程的切换。协程
重点:遇到io切换的时候才有意义进程
具体: 协程概念本质是程序员抽象出来的,操做系统根本不知道协程存在,也就说来了一个线程我本身遇到io 我本身线程内部直接切到本身的别的任务上了,操做系统跟本发现不了,也就是实现了单线程下效率最高.
优势:
缺点:
特色:
import time def eat(): print('eat 1') # 疯狂的计算呢没有io time.sleep(2) # for i in range(100000000): # i+1 def play(): print('play 1') # 疯狂的计算呢没有io time.sleep(3) # for i in range(100000000): # i+1 play() eat() # 5s
在单线程里,利用yield来实现协程,这是一个没有意义的携程(由于咱们说过协程要作在有io的状况下才有意义)
import time def func1(): while True: 1000000+1 yield def func2(): g = func1() for i in range(100000000): i+1 next(g) start = time.time() func2() stop = time.time() print(stop - start) # 17.68560242652893
对比上面yeild切换运行的时间,反而比咱们单独取执行函数串行更消耗时间,因此上面实现的携程是没有意义的。
import time def func1(): for i in range(100000000): i+1 def func2(): for i in range(100000000): i+1 start = time.time() func1() func2() stop = time.time() print(stop - start) # 12.08229374885559
协程的本质就是在单线程下,由用户本身控制一个任务遇到io阻塞了就切换另一个任务去执行,以此来提高效率。为了实现它,咱们须要找寻一种能够同时知足如下条件的解决方案:
重点:使用gevent来实现协程是能够的,可是咱们说过协程最主要是遇到IO才有意义,可是刚好这个gevent模块作不到协程的真正的意义,也就是说这个而模块他检测不到IO
但用gevent模块是检测不到IO的,也就是说这样写一样是没有意义的
下面程序里的gevent是一个类
gevent.spawn本质调用了gevent.greenlet.Greenlet的类的静态方法spawn:
@classmethod def spawn(cls, *args, **kwargs): g = cls(*args, **kwargs) g.start() return g
这个类方法调用了Greenlet类的两个函数,*__init_*_ 和 start. init函数中最为关键的是这段代码:
def __init__(self, run=None, *args, **kwargs): greenlet.__init__(self, None, get_hub()) # 将新创生的greenlet实例的parent一概设置成hub if run is not None: self._run = run
# 在这段程序咱们发现,这段程序并无实现碰见IO的时候,用户模cpu实现任务切换 import gevent import time def eat(): print('eat 1') time.sleep(2) print('eat 2') def play(): print('play 1') # 疯狂的计算呢没有io time.sleep(3) print('play 2') start = time.time() g1 = gevent.spawn(eat) g2 = gevent.spawn(play) g1.join() g2.join() end = time.time() print(end-start) 5.0041022300720215 ''' 结果: eat 1 eat 2 play 1 play 2 5.004306077957153 '''
重点二:使用gevent的一个补丁来实现,经过gevent类来实现真正有意义的协程,用户真正的实现里以操做系统发现不了的方式,模拟了碰见IO的时候实现任务之间的来回切换
注意:这里再次强调,协程的本质意义是在单线程内实现任务的保存状态加切换,而且真正的协程必须是在遇到IO的状况
from gevent import monkey;monkey.patch_all() import gevent import time def eat(): print('eat 1') time.sleep(2) print('eat 2') def play(): print('play 1') # 疯狂的计算呢没有io time.sleep(3) print('play 2') start = time.time() g1 = gevent.spawn(eat) g2 = gevent.spawn(play) g1.join() g2.join() end = time.time() print(end-start)# 3.003168821334839 ''' 结果: eat 1 play 1 eat 2 play 2 3.003168821334839 '''