一、我们知道无论是创建多进程还是创建多线程池来解决问题,都要消耗一定的时间来创建进程、创建线程、以及管理他们之间的切换。
基于单线程来实现并发,这样就可以节省创建线程进程所消耗的时间。
二、如何实现在两个函数之间的切换?
def func1(): print(1) yield print(3) yield def func2(): g = func1() next(g) print(2) next(g) print(4) func2() ‘‘‘ 1 2 3 4 ‘‘‘
def consumer(): while True: n = yield print(‘消费了一个包子%s‘%n) def producer(): g = consumer() next(g) for i in range(5): print(‘生产了包子%s‘%i) g.send(i) producer() ‘‘‘ 生产了包子0 消费了一个包子0 生产了包子1 消费了一个包子1 生产了包子2 消费了一个包子2 生产了包子3 消费了一个包子3 生产了包子4 消费了一个包子4 ‘‘‘
import time def consumer(): ‘‘‘任务1:接收数据,处理数据‘‘‘ while True: x=yield def producer(): ‘‘‘任务2:生产数据‘‘‘ g=consumer() next(g) for i in range(10000000): g.send(i) time.sleep(2) start=time.time() producer() #并发执行,但是任务producer遇到io就会阻塞住,并不会切到该线程内的其他任务去执行 stop=time.time() print(stop-start)
对于单线程下,程序中不可避免的会出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能再一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程。
三、协程
协程:是单线程下的并发,协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
协程的本质:在但线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。
需要强调的是:
1:python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其它线程运行)。
2:单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(非io操作的切换与效率无关)
对比操作系统控制线程的切换,用户在单线程内控制协程的切换的优缺点:
优点:
1.协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级。
2.单线程内就可以实现并发的效果,最大限度地利用cpu。
缺点:
1.协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程。
2.协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程。
协程的特点:
1.必须在只有一个单线程里实现并发
2.修改共享数据不需加锁
3.用户程序里自己保存多个控制流的上下文栈
4.附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))
四、greenlet模块
from greenlet import greenlet def eat(name): print(‘%s eat 1‘ %name) g2.switch(‘haha‘) print(‘%s eat 2‘ %name) g2.switch() def play(name): print(‘%s play 1‘ %name) g1.switch() print(‘%s play 2‘ %name) g1=greenlet(eat) g2=greenlet(play) g1.switch(‘hjm‘)#可以在第一次switch时传入参数,以后都不需要 ‘‘‘ hjm eat 1 haha play 1 hjm eat 2 haha play 2 ‘‘‘
import time from greenlet import greenlet # 在单线程中切换状态的模块 def eat1(): print(‘吃鸡腿1‘) g2.switch() time.sleep(5) print(‘吃鸡翅2‘) g2.switch() def eat2(): print(‘吃饺子1‘) g1.switch() time.sleep(3) print(‘白切鸡‘) g1 = greenlet(eat1) g2 = greenlet(eat2) g1.switch() ‘‘‘ 吃鸡腿1 吃饺子1 吃鸡翅2 白切鸡 ‘‘‘
单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度。
#顺序执行 import time def f1(): res=1 for i in range(100000000): res+=i def f2(): res=1 for i in range(100000000): res*=i start=time.time() f1() f2() stop=time.time() print(‘run time is %s‘ %(stop-start)) # run time is 10.494175910949707 #切换 from greenlet import greenlet import time def f1(): res=1 for i in range(100000000): res+=i g2.switch() def f2(): res=1 for i in range(100000000): res*=i g1.switch() start=time.time() g1=greenlet(f1) g2=greenlet(f2) g1.switch() stop=time.time() print(‘run time is %s‘ %(stop-start)) # run time is 63.0725622177124
greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。
五、gevent模块