一、协程的基本性质
1、概念:协程是由用户程序自己控制调度的,是一种用户态的轻量级线程
2、作用:用来实现单线程下实现并发,从应用程序级别控制单线程下多任务的切换,注意一定是遇到IO才切且不可能有并行的概念
3、优缺点
(1)优点:
》协程的切换速度相比操作系统切换速度要小得多
》隐藏自己的IO阻塞,使程序尽可能的处在就绪态,争取更多的执行权限,来“骗”操作系统识别不到线程的IO操作
(2)缺点:
》无法利用多核优势
》一旦协程出现不可挽回的IO阻塞(因为不可能真正的隐藏IO阻塞),会阻塞整个线程
4、特点:
》必须在单线程的前提下
》修改共享数据不用加锁
二、代码实现协程
1、greenlet模块(第三方库)
(1)方法
g=greenlet(函数名) 得到一个协程对象
g.switch(参数) 切换到g去执行,第一次切的时候传参数,后面再切换就不用传参了
(2)特点
但是,遇到IO阻塞是无法自动切换的,但是比yield切换更方便点
#greenlet与yield一样都无法检测io然后自动切换,唯一一点好处是:greenlet比yieled的切换方式更加方便from greenlet import greenletimport timedef eat(name): print('%s eat 1' %name) time.sleep(10) g2.switch('alex') 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('alex')
2、gevent模块(第三方库):真正实现切换的效果
(1)作用:能够做到自己检测IO,并实现自动切换
(2)方法:
g=gevent.spawn(函数名,参数) 得到一个协程对象,异步提交任务
g.join() 等到g结束
gevent.joinall([g1,g2])等g1,g2都结束,注意是列表形式
g.value 取g的运行结果,注意要join后或者是运行完后才能拿结果,spawn后立刻取结果可能是None
gevent.sleep() 也是睡几秒,相当于IO阻塞
gevent只能直接识别自己的IO阻塞,若是time.sleep()就不会直接识别也就不会自动切换,要想解决这个问题,就必须在导入gevent之前先加补丁:
from gevent import monkeymonkey.patch_all()或者
from gevent import monkey;monkey.patch_all()
这样协程就能识别所有的IO阻塞
from gevent import monkey;monkey.patch_all() #这样就可以识别所有的IO阻塞import geventimport osfrom threading import current_thread#1.检测IO#2.自动切换import timedef eat(): print('%s eat 1' %current_thread().getName()) time.sleep(2) print('%s eat 2' %current_thread().getName())def play(): print('%s play 1' %current_thread().getName()) time.sleep(1) print('%s play 2' %current_thread().getName())start=time.time()g1=gevent.spawn(eat,)g2=gevent.spawn(play,)# g1.join()# g2.join()gevent.joinall([g1,g2])stop=time.time()print(stop-start)
3、基于协程的单线程并发
客户端:
from threading import Threadfrom socket import *def client(): c=socket(AF_INET,SOCK_STREAM) c.connect(('127.0.0.1',8092)) while True: c.send('hello'.encode('utf-8')) data=c.recv(1024) print(data.decode('utf-8'))if __name__ == '__main__': for i in range(500): t=Thread(target=client) t.start()
服务端:
from gevent import monkey;monkey.patch_all()import geventfrom multiprocessing import Processfrom socket import *def server(ip,port): s = socket(AF_INET, SOCK_STREAM) s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) s.bind((ip,port)) s.listen(5) while True: conn,addr=s.accept() print('%s:%s' % (addr[0], addr[1])) g1=gevent.spawn(talk,conn,addr)def talk(conn,addr): while True: try: data=conn.recv(1024) print('%s:%s [%s]' %(addr[0],addr[1],data)) if not data:break conn.send(data.upper()) except ConnectionResetError: break conn.close()if __name__ == '__main__': server('127.0.0.1',8092)