(一)簡介python
首先咱們得知道協程是啥?協程其實能夠認爲是比線程更小的執行單元。 爲啥說他是一個執行單元,由於他自帶CPU上下文。這樣只要在合適的時機, 咱們能夠把一個協程切換到另外一個協程。 只要這個過程當中保存或恢復 CPU上下文那麼程序仍是能夠運行的。算法
通俗的理解:協程可當作在一個線程中的某個函數,能夠在任何地方保存當前函數的一些臨時變量等信息,而後切換到另一個函數中執行,注意不是經過調用函數的方式作到的,而且切換的次數以及何時再切換到原來的函數都由開發者本身肯定緩存
(二)區別網絡
那麼這個過程看起來比線程差很少。其實否則, 線程切換從系統層面遠不止保存和恢復 CPU上下文這麼簡單。 操做系統爲了程序運行的高效性每一個線程都有本身緩存Cache等等數據,操做系統還會幫你作這些數據的恢復操做。 因此線程的切換很是耗性能。可是協程的切換隻是單純的操做CPU的上下文,因此一秒鐘切換個上百萬次系統都抗的住。可是協程有一個問題,就是系統並不感知,因此操做系統不會幫你作切換。 那麼誰來幫你作切換?讓須要執行的協程更多的得到CPU時間纔是問題的關鍵。框架
目前的協程框架通常都是設計成 1:N 模式。所謂 1:N 就是一個線程做爲一個容器裏面放置多個協程。 那麼誰來適時的切換這些協程?答案是協程本身主動讓出CPU,也就是每一個協程池裏面有一個調度器, 這個調度器是被動調度的。意思就是他不會主動調度。而是當一個協程發現本身執行不下去了(好比異步等待網絡的數據回來,可是當前尚未數據到), 這個時候就能夠由這個協程通知調度器,這個時候執行到調度器的代碼,調度器根據事先設計好的調度算法找到當前最須要CPU的協程。 切換這個協程的CPU上下文把CPU的運行權交個這個協程,直到這個協程出現執行不下去須要等等的狀況,或者它調用主動讓出CPU的API之類,觸發下一次調度。異步
(三)優勢socket
在IO密集型的程序中因爲IO操做遠遠慢於CPU的操做,因此每每須要CPU去等IO操做。 同步IO下系統須要切換線程,讓操做系統能夠在IO過程當中執行其餘的東西。 這樣雖然代碼是符合人類的思惟習慣可是因爲大量的線程切換帶來了大量的性能的浪費,尤爲是IO密集型的程序。可是協程能夠很好解決這個問題。好比 把一個IO操做 寫成一個協程。當觸發IO操做的時候就自動讓出CPU給其餘協程。要知道協程的切換很輕的。 協程經過這種對異步IO的封裝 既保留了性能也保證了代碼的容易編寫和可讀性。在高IO密集型的程序下很好。可是高CPU密集型的程序下沒啥好處。函數
(四)簡單實現性能
1 import time 2 3 def A(): 4 while True: 5 print("----A---") 6 yield 7 time.sleep(0.5) 8 9 def B(c): 10 while True: 11 print("----B---") 12 c.next() 13 time.sleep(0.5) 14 15 if __name__=='__main__': 16 a = A() 17 B(a) 18 19 20 >>>輸出: 21 --B-- 22 --A-- 23 --B-- 24 --A-- 25 --B-- 26 --A-- 27 。。。省略。。。
(五)協程-greenlet版spa
爲了更好使用協程來完成多任務,python中的greenlet模塊對其封裝,從而使得切換任務變的更加簡單。
1 #coding=utf-8 2 3 from greenlet import greenlet 4 import time 5 6 def test1(): 7 while True: 8 print "---A--" 9 gr2.switch() 10 time.sleep(0.5) 11 12 def test2(): 13 while True: 14 print "---B--" 15 gr1.switch() 16 time.sleep(0.5) 17 18 gr1 = greenlet(test1) 19 gr2 = greenlet(test2) 20 21 #切換到gr1中運行 22 gr1.switch()
(六)協程-gevent版
greenlet已經實現了協程,可是這個還的人工切換,是否是以爲太麻煩了,不要捉急,python還有一個比greenlet更強大的而且可以自動切換任務的模塊 gevent
其原理是當一個greenlet遇到IO(指的是input output 輸入輸出,好比網絡、文件操做等)操做時,好比訪問網絡,就自動切換到其餘的greenlet,等到IO操做完成,再在適當的時候切換回來繼續執行。
因爲IO操做很是耗時,常常使程序處於等待狀態,有了gevent爲咱們自動切換協程,就保證總有greenlet在運行,而不是等待IO。
1 import sys 2 import time 3 import gevent 4 5 from gevent import socket,monkey 6 monkey.patch_all() 7 8 def handle_request(conn): 9 while True: 10 data = conn.recv(1024) 11 if not data: 12 conn.close() 13 break 14 print("recv:", data) 15 conn.send(data) 16 17 18 def server(port): 19 s = socket.socket() 20 s.bind(('', port)) 21 s.listen(5) 22 while True: 23 cli, addr = s.accept() 24 gevent.spawn(handle_request, cli) 25 26 if __name__ == '__main__': 27 server(7788)