python-day37--协程
一、 协程介绍
单线程下实现并发,提升运行效率,
1.自己控制切换,保存状态
2.遇到I/O切 (单纯的CPU切没意义,只有在遇到I/O的时候切才有效率)
一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。、
需要强调的是:
#1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行) #2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
对比操作系统控制线程的切换,用户在单线程内控制协程的切换
优点如下:
#1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级 #2. 单线程内就可以实现并发的效果,最大限度地利用cpu
缺点如下:
#1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程 #2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
总结协程特点:
- 必须在只有一个单线程里实现并发
- 修改共享数据不需加锁
- 用户程序里自己保存多个控制流的上下文栈
- 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))
二、用yeild实现协程
1 import time 2 def init(func): 3 def wrapper(*args,**kwargs): 4 g=func(*args,**kwargs) 5 next(g) 6 return g 7 return wrapper 8 @init 9 def consumer(): 10 while True: 11 x=yield 12 print(x) 13 14 def producer(target): 15 for i in range(10): 16 # time.sleep(1) 17 target.send(i) 18 19 producer(consumer())
三、greenlet模块(了解就行,遇到I/O不能切)
1 from greenlet import greenlet 2 import time 3 def eat(name): 4 print('%s eat 1' %name) 5 time.sleep(10) #在io阻塞的时候 不能来回切,会一直在这睡着 6 g2.switch('egon') 7 print('%s eat 2' %name) 8 g2.switch() 9 def play(name): 10 print('%s play 1' %name) 11 g1.switch() 12 print('%s play 2' %name) 13 14 g1=greenlet(eat) 15 g2=greenlet(play) 16 17 #g1.switch('egon')#可以在第一次switch时传入参数,以后都不需要
四、gevent模块
遇到IO阻塞时会自动切换任务
用法
#用法 g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的 g2=gevent.spawn(func2) g1.join() #等待g1结束 g2.join() #等待g2结束 #或者上述两步合作一步:gevent.joinall([g1,g2]) g1.value#拿到func1的返回值
1 from gevent import monkey;monkey.patch_all() #monkey;monkey.patch_all() 可以识别其他的I/O操作 2 import gevent 3 import time,threading 4 def eat(name): 5 print('%s eat 1' %name) 6 time.sleep(2) 7 print('%s eat 2' %name) 8 return 'eat' 9 10 def play(name): 11 print('%s play 1' %name) 12 time.sleep(3) 13 print('%s play 2' %name) 14 return 'play' 15 16 start=time.time() 17 g1=gevent.spawn(eat,'egon') 18 g2=gevent.spawn(play,'egon') 19 # g1.join() 20 # g2.join() 21 gevent.joinall([g1,g2]) 22 print('主',(time.time()-start)) 23 print(g1.value) 24 print(g2.value) 25 26 # 结果: 27 egon eat 1 28 egon play 1 29 egon eat 2 30 egon play 2 31 主 3.0018091201782227 32 eat 33 play
五、协程的应用
练习题1
1 from gevent import monkey;monkey.patch_all() 2 import gevent 3 import requests 4 import time 5 6 def get_page(url): 7 print('GET: %s' %url) 8 response=requests.get(url) 9 if response.status_code == 200: 10 print('%d bytes received from %s' %(len(response.text),url)) 11 12 start_time=time.time() 13 14 # get_page('https://www.python.org/') #串行 15 # get_page('https://www.yahoo.com/') #串行 16 # get_page('https://github.com/') #串行 17 18 g1=gevent.spawn(get_page, 'https://www.python.org/') 19 g2=gevent.spawn(get_page, 'https://www.yahoo.com/') 20 g3=gevent.spawn(get_page, 'https://github.com/') 21 22 gevent.joinall([g1,g2,g3]) 23 stop_time=time.time() 24 print('run time is %s' %(stop_time-start_time))
练习题2
1 from gevent import monkey;monkey.patch_all() 2 import gevent 3 from socket import * 4 def talk(conn,addr): 5 while True: 6 data=conn.recv(1024) 7 print('%s:%s %s' %(addr[0],addr[1],data)) 8 conn.send(data.upper()) 9 conn.close() 10 11 def server(ip,port): 12 s = socket(AF_INET, SOCK_STREAM) 13 s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) 14 s.bind((ip,port)) 15 s.listen(5) 16 while True: 17 conn,addr=s.accept() 18 gevent.spawn(talk,conn,addr) 19 s.close() 20 21 if __name__ == '__main__': 22 server('127.0.0.1', 8088)
1 from multiprocessing import Process 2 from socket import * 3 def client(server_ip,server_port): 4 client=socket(AF_INET,SOCK_STREAM) 5 client.connect((server_ip,server_port)) 6 while True: 7 cmd=input('') 8 client.send(cmd.encode('utf-8')) 9 msg=client.recv(1024) 10 print(msg.decode('utf-8')) 11 12 if __name__ == '__main__': 13 client('127.0.0.1', 8088)