python-day37--協程
阿新 • • 發佈:2017-08-31
view reading 不能 應用程序級別 upper switch receive cer 控制
一、 協程介紹
單線程下實現並發,提升運行效率,
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 forView Codei 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時傳入參數,以後都不需要View Code
四、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)並發通信 客戶端
python-day37--協程