python网络编程-协程(协程说明,greenlet,gevent)
一:什么是协程
协程(Coroutine):,又称微线程。协程是一种用户态的轻量级线程。是由用户自己控制,CPU根本不知道协程存在。
协程拥有自己的寄存器上下文和栈。
协程调度切换时,将寄存器上下文和栈保存在其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈
因此:协程能保留上一次调用的时的状态,每次过程重入时,就相当于进入上一次调用的。
换种说法:进入上一次离开时所处逻辑流的位置。
注意:线程切换会保存到CPU的寄存器里。
协程的标准:
1)必须在只有一个单线程里实现并发
2)修改共享数据不需要加锁
3)用户程序里自己保存从个控制流的上下文栈
4)一个协程遇到IO操作自动切换到其他协程
二:协程在什么时候切换
在什么时候进程切换:遇到I/O操作就切换,协程就是把io踢掉了(因为IO耗时)。
什么时候切回去: I0操作调用完了,通过调用callback切换回去
三:协程的优点缺点
优点:
1)无需线程上下文切换的开销
2)无需原子操作锁定及同步的开销(因为协程就是单线程,它就是串行,同一时间改数据只有一个线程)
3)方便切换控制流,简化编程模型
4)高并发+高扩展性+低成本:一个CPU支持上万的协程不是问题,很适合高并发
缺点:
1)无法利用多核资源:协程本质是单线程,他不能同时单个CPU的多个核用上,协程需要和进程配合
才能运行在多CPU上。
2)进行阻塞(Blocking)操作(如IO时)会阻塞整个程序
四:yield实现切换
# -*- coding:utf-8 -*- __author__ = 'shisanjun' import time import queue def consumer(name): print("------->starting eating baozi...") while True: new_baozi=yield print("[%s] is eating baozi %s" %(name,new_baozi)) def producer(): r=con.__next__() #con=consumer("c1")#只是生成生成器,不会执行,所以先要调用next才会开始执行 r=con2.__next__() n=0 while n <5: n+=1 con.send(n)#两个作业,唤醒生成器,并赋值 con2.send(n) print("\033[32;1m[producer]\033[0m is making baozi %s" %n) if __name__=='__main__': con=consumer("c1") #生成生成器 con2=consumer("c2") p=producer() """ ------->starting eating baozi... ------->starting eating baozi... [c1] is eating baozi 1 [c2] is eating baozi 1 [producer] is making baozi 1 [c1] is eating baozi 2 [c2] is eating baozi 2 [producer] is making baozi 2 [c1] is eating baozi 3 [c2] is eating baozi 3 [producer] is making baozi 3 [c1] is eating baozi 4 [c2] is eating baozi 4 [producer] is making baozi 4 [c1] is eating baozi 5 [c2] is eating baozi 5 [producer] is making baozi 5 """
我们刚才用yield实现一个简单的协程,实现单线程多并发。
五:Greenlet
greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator
# -*- coding:utf-8 -*- __author__ = 'shisanjun' from greenlet import greenlet def test1(): print(12) gr2.switch() print(34) gr2.switch() def test2(): print(56) gr1.switch() print(78) gr1=greenlet(test1)#起动一个协程 gr2=greenlet(test2) gr1.switch() #从test1开始
上面代码切换过程
没有解决一个问题,就是遇到IO操作,自动切换,手动切换。下面实现自动切换
六:Gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet,
它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
# -*- coding:utf-8 -*- __author__ = 'shisanjun' import gevent def func1(): print("\033[31;1m 李在搞锗\033[0m") gevent.sleep(2)#遇到sleep自动切换,模拟IO操作 print("\033[31;1m 李在又继续搞锗。。。。\033[0m") def func2(): print(("\033[32;1m 李切换搞牛。。。\033[0m")) gevent.sleep(1)#遇到sleep自动切换 print(("\033[32;1m 李切换继续搞牛。。。\033[0m")) gevent.joinall( [ gevent.spawn(func1),#可以带多个参数,第一个为函数名,第二个为函数参数 gevent.spawn(func2) ] ) """ 李在搞锗 李切换搞牛。。。 李切换继续搞牛。。。 李在又继续搞锗。。。。 """
七: 同步与异步的性能区别
# -*- coding:utf-8 -*- __author__ = 'shisanjun' import gevent def task(pid): gevent.sleep(0.5) print('Task %s done' %pid) def synchronous(): for i in range(1,10): task(i) def asynchronous(): theads=[gevent.spawn(task,i) for i in range(10)] gevent.joinall(theads) print("synchronous") synchronous() #顺序执行,结果是一个个出来 print("asynchronous") asynchronous() #并发执行,结果几乎同时出来 """ synchronous Task 1 done Task 2 done Task 3 done Task 4 done Task 5 done Task 6 done Task 7 done Task 8 done Task 9 done asynchronous Task 0 done Task 9 done Task 8 done Task 7 done Task 6 done Task 5 done Task 4 done Task 3 done Task 2 done Task 1 done """
上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn
。
初始化的greenlet列表存放在数组threads
中,此数组被传给gevent.joinall
函数,
后者阻塞当前流程,并执行所有给定的greenlet。执行流程只会在 所有greenlet执行完后才会继续向下走。
八:自动遇到IO切换
Gevent 默认不知道urllib,socket做了IO操作,所以打补厅,增加monkey.patch_all()
# -*- coding:utf-8 -*- __author__ = 'shisanjun' from gevent import monkey import gevent from urllib.request import urlopen #gevent默认检测不到ulrlib,所以默认是阻塞的,要加monkey实现自动切换 monkey.patch_all()#实现遇到IO就自动切换 def f(url): print('Get %s '%url) resp=urlopen(url)#这里自动切换了 data=resp.read() print("%d bytes received from %s." %(len(data),data)) gevent.joinall([ gevent.spawn(f,"https://www.baidu.com"), gevent.spawn(f,"https://www.360.cn"), ])
九:通过gevent实现单线程下的多socket并发
# -*- coding:utf-8 -*- __author__ = 'shisanjun' import sys import socket import time import gevent from gevent import socket,monkey monkey.patch_all() def server(port): s=socket.socket() s.bind(("0.0.0.0",port)) s.listen(100) while True: conn,addr=s.accept() gevent.spawn(handle_request,conn) def handle_request(conn): try: while True: data=conn.recv(1024) print('recv:',data) conn.send(data) if not data: conn.shutdown(socket.SHUT_WR) except Exception as e: print(e) finally: conn.close() if __name__=="__main__": server(8001)
# -*- coding:utf-8 -*- __author__ = 'shisanjun' import socket import threading def sock_conn(): client = socket.socket() client.connect(("localhost",8001)) count = 0 while True: #msg = input(">>:").strip() #if len(msg) == 0:continue client.send( ("hello %s" %count).encode("utf-8")) data = client.recv(1024) print("[%s]recv from server:" % threading.get_ident(),data.decode()) #结果 count +=1 client.close() for i in range(100): t = threading.Thread(target=sock_conn) t.start() #并发100个sock连接
本文没有解决:什么时候切换回来