一:什麼是協程python
協程(Coroutine):,又稱微線程。協程是一種用戶態的輕量級線程。是由用戶本身控制,CPU根本不知道協程存在。編程
協程擁有本身的寄存器上下文和棧。數組
協程調度切換時,將寄存器上下文和棧保存在其餘地方,在切回來的時候,恢復先前保存的寄存器上下文和棧併發
所以:協程能保留上一次調用的時的狀態,每次過程重入時,就至關於進入上一次調用的。異步
換種說法:進入上一次離開時所處邏輯流的位置。socket
注意:線程切換會保存到CPU的寄存器裏。async
協程的標準:ide
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鏈接
本文沒有解決:何時切換回來