python3 協程gevent介紹並實現多客戶端併發通信socket

協程的概念

協程,又稱微線程,纖程。英文名Coroutine。

線程是系統級別的它們由操作系統調度,而協程則是程序級別的由程序根據需要自己調度。在一個線程中會有很多函數,我們把這些函數稱爲子程序,在子程序執行過程中可以中斷去執行別的子程序,而別的子程序也可以中斷回來繼續執行之前的子程序,這個過程就稱爲協程。也就是說在同一線程內一段代碼在執行過程中會中斷然後跳轉執行別的代碼,接着在之前中斷的地方繼續開始執行,類似與yield操作。

協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧。因此:協程能保留上一次調用時的狀態(即所有局部狀態的一個特定組合),每次過程重入時,就相當於進入上一次調用的狀態,換種說法:進入上一次離開時所處邏輯流的位置。

協程的優點:

  (1)無需線程上下文切換的開銷,協程避免了無意義的調度,由此可以提高性能(但也因此,程序員必須自己承擔調度的責任,同時,協程也失去了標準線程使用多CPU的能力)

  (2)無需原子操作鎖定及同步的開銷

  (3)方便切換控制流,簡化編程模型

  (4)高併發+高擴展性+低成本:一個CPU支持上萬的協程都不是問題。所以很適合用於高併發處理。

協程的缺點:

  (1)無法利用多核資源:協程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協程需要和進程配合才能運行在多CPU上.當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。

  (2)進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程序

協程簡單案例,幫助理解

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import gevent
def foo():
    print('running in foo')
    gevent.sleep(2)
    print('com back from bar in to foo')
def bar():
    print('running in bar')
    gevent.sleep(2)
    print('com back from foo in to bar')

gevent.joinall([      #創建線程並行執行程序,碰到IO就切換
    gevent.spawn(foo),
    gevent.spawn(bar),
])

返回結果 

說明:

gevent.spawn(foo)  gevent.spawn(bar),首先執行foo方法中print('running in foo'),foo方法睡眠2秒,此時協程捕獲到foo方法中斷,轉去執行bar方法,print('running in bar'),此時bar方法也睡眠2秒,協程返回foo方法繼續執行print('com back from bar in to foo'),foo方法執行完畢,再去繼續執行bar方法後續邏輯print('com back from foo in to bar'),因此出現如上結果。

利用協程的這個用途,可對客戶端引入協程監控調度,多個客戶併發操作時,循環處理,如下案例

服務端代碼:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket,gevent
from gevent import monkey
monkey.patch_all()

def server_sock(port):
    s = socket.socket()
    s.bind(('127.0.0.1',port))
    s.listen(10)
    while True:
        conn,addr = s.accept()
        gevent.spawn(handle_request,conn)
def handle_request(conn):
    try:
        while True:
            data = conn.recv(1024)
            if not data: conn.shutdown(socket.SHUT_WR)
            print('recv:',data.decode())
            conn.send(data)
    except Exception as ex:
        print(ex)
    finally:
        conn.close()

if __name__ == '__main__':
    server_sock(8888)
客戶端代碼:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
HOST = '127.0.0.1'  # The remote host
PORT = 8888  # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
    msg=input("input:")
    s.send(msg.encode())
    data = s.recv(1024)
    print('Received', repr(data))
s.close()

結果:

 

print('running in foo')

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章