22-Python-協程

一、協程的概念

協程,又稱微線程,纖程。英文名Coroutine。一句話說明什麼是線程:協程是一種用戶態的輕量級線程html

協程擁有本身的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其餘地方,在切回來的時候,恢復先前保存的寄存器上下文和棧。所以:python

協程能保留上一次調用時的狀態(即全部局部狀態的一個特定組合),每次過程重入時,就至關於進入上一次調用的狀態,換種說法:進入上一次離開時所處邏輯流的位置。git

協程的好處:github

    • 無需線程上下文切換的開銷
    • 無需原子操做鎖定及同步的開銷
      • "原子操做(atomic operation)是不須要synchronized",所謂原子操做是指不會被線程調度機制打斷的操做;這種操做一旦開始,就一直運行到結束,中間不會有任何 context switch (切換到另外一個線程)。原子操做能夠是一個步驟,也能夠是多個操做步驟,可是其順序是不能夠被打亂,或者切割掉只執行部分。視做總體是原子性的核心。
    • 方便切換控制流,簡化編程模型
    • 高併發+高擴展性+低成本:一個CPU支持上萬的協程都不是問題。因此很適合用於高併發處理。

缺點:編程

    • 沒法利用多核資源:協程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協程須要和進程配合才能運行在多CPU上.固然咱們平常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
    • 進行阻塞(Blocking)操做(如IO時)會阻塞掉整個程序

符合一下條件就能稱之爲協程:併發

  1. 必須在只有一個單線程裏實現併發
  2. 修改共享數據不需加鎖
  3. 用戶程序裏本身保存多個控制流的上下文棧
  4. 一個協程遇到IO操做自動切換到其它協程

 

二、greenlet

greenlet是一個用C實現的協程模塊,相比與python自帶的yield,它可使你在任意函數之間隨意切換,而不需把這個函數先聲明爲generator異步

 1 from greenlet import greenlet
 2 
 3 
 4 def func1():
 5     print("第一條打印信息")  # step2
 6     gr2.switch()  # step3
 7     print("這兒是第三條打印信息")  # step6
 8     gr2.switch()  # step7
 9 
10 
11 def func2():
12     print("這裏纔是第二條打印信息")  # step4
13     gr1.switch()  # step5
14     print("老哥,這纔是最後一條打印信息")  # step8
15 
16 
17 gr1 = greenlet(func1)
18 gr2 = greenlet(func2)
19 gr1.switch()  # step1

 

三、Gevent

Gevent 是一個第三方庫,能夠輕鬆經過gevent實現併發同步或異步編程,在gevent中用到的主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協程。 Greenlet所有運行在主程序操做系統進程的內部,但它們被協做式地調度。socket

 1 import gevent
 2 
 3 
 4 def func1():
 5     print("func1函數打印的第一條消息...")  # step1
 6     gevent.sleep(2)  # step2。一碰到I/O操做就自動切換
 7     print("func1函數切換回來打印的消息...")
 8 
 9 
10 def func2():
11     print("func2函數打印的第一條消息...")  # step3
12     gevent.sleep(1)  # step4
13     print("func2函數切換回來打印的消息...")
14 
15 
16 def func3():
17     print("func3函數打印的第一條消息...")  # step5
18     gevent.sleep(0)  # step6
19     print("func3函數切換回來打印的消息...")
20 
21 
22 gevent.joinall([
23     gevent.spawn(func1),
24     gevent.spawn(func2),
25     gevent.spawn(func3)
26 ])
27 
28 # 輸出結果以下:
29 func1函數打印的第一條消息...
30 func2函數打印的第一條消息...
31 func3函數打印的第一條消息...
32 func3函數切換回來打印的消息...
33 func2函數切換回來打印的消息...
34 func1函數切換回來打印的消息...

 

四、同步和異步的性能差異

 1 from urllib import request
 2 import gevent
 3 import time
 4 from gevent import monkey
 5 monkey.patch_all()  # 把當前程序的全部I/O操做都單獨作上標記
 6 
 7 
 8 def f(url):
 9     print("GET: %s" % url)
10     resp = request.urlopen(url)
11     data = resp.read()
12     print("%d bytes received from %s" % (len(data), url))
13     # f = open("url.html", "wb")
14     # f.write(data)
15     # f.close()
16 
17 
18 urls = ["https://www.python.org",
19         "https://www.yahoo.com",
20         "https://github.com",
21         ]
22 start_time = time.time()
23 for url in urls:
24     f(url)
25 
26 print("同步抓取網頁花費的時間:", time.time() - start_time)
27 
28 print("下面是異步".center(50, "-"))
29 
30 async_start_time = time.time()
31 gevent.joinall([
32         gevent.spawn(f, "https://www.python.org"),
33         gevent.spawn(f, "https://www.yahoo.com"),
34         gevent.spawn(f, "https://github.com"),
35         ]
36 )
37 
38 print("異步抓取網頁花費的時間:", time.time() - async_start_time)
39 
40 # 打印結果:
41 
42 GET: https://www.python.org
43 48860 bytes received from https://www.python.org
44 GET: https://www.yahoo.com
45 527907 bytes received from https://www.yahoo.com
46 GET: https://github.com
47 51878 bytes received from https://github.com
48 同步抓取網頁花費的時間: 6.071749210357666
49 ----------------------下面是異步-----------------------
50 GET: https://www.python.org
51 GET: https://www.yahoo.com
52 GET: https://github.com
53 48860 bytes received from https://www.python.org
54 51878 bytes received from https://github.com
55 528360 bytes received from https://www.yahoo.com
56 異步抓取網頁花費的時間: 2.586545467376709

 

五、經過gevent實現單線程下的多socket併發

 1 import socket
 2 import gevent
 3 
 4 from gevent import socket, monkey
 5 
 6 monkey.patch_all()
 7 
 8 
 9 def server(ip, port):
10     s = socket.socket()
11     s.bind((ip, port))
12     s.listen(500)
13     while True:
14         cli, addr = s.accept()
15         # print(cli)
16         print("創建與IP[%s] PORT[%s]的客戶端鏈接" % (addr[0], addr[1]))
17         gevent.spawn(handle_request, cli)
18 
19 
20 def handle_request(conn):
21     try:
22         while True:
23             print("等待接收數據...")
24             data = conn.recv(1024)
25             print("received:", data.decode("utf-8"))
26             conn.send(data.upper())
27             if not data:
28                 conn.shutdown(socket.SHUT_WR)
29 
30     except Exception as ex:
31         print(ex)
32     finally:
33         conn.close()
34 
35 
36 if __name__ == "__main__":
37     print("正在監聽客戶端鏈接...")
38     server("0.0.0.0", 8001)
相關文章
相關標籤/搜索