1. 程式人生 > >Python實戰之協程(greenlet模組,gevent模組,socket+ gevent實現高併發處理)

Python實戰之協程(greenlet模組,gevent模組,socket+ gevent實現高併發處理)

協程

協程,又稱微執行緒,纖程。英文名Coroutine。一句話說明什麼是執行緒:協程是一種使用者態的輕量級執行緒。(cpu不知道,是使用者自己控制的)

協程擁有自己的暫存器上下文和棧。協程排程切換時,將暫存器上下文和棧儲存到其他地方,在切回來的時候,恢復先前儲存的暫存器上下文和棧(執行緒的上下文切換是儲存在CPU,協程就不是)。因此:

協程能保留上一次呼叫時的狀態(即所有區域性狀態的一個特定組合),每次過程重入時,就相當於進入上一次呼叫的狀態,換種說法:進入上一次離開時所處邏輯流的位置(yield生產消費就是協程)。

 

協程的好處

無需執行緒上下文切換的開銷

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

  "原子操作(atomic operation)是不需要synchronized",所謂原子操作是指不會被執行緒排程機制打斷的操作;這種操作一旦開始,就一直執行到結束,中間不會有任何 context switch (切換到另一個執行緒)。原子操作可以是一個步驟,也可以是多個操作步驟,但是其順序是不可以被打亂,或者切割掉只執行部分。視作整體是原子性的核心。

方便切換控制流,簡化程式設計模型

高併發+高擴充套件性+低成本:一個CPU支援上萬的協程都不是問題。所以很適合用於高併發處理。

缺點:

無法利用多核資源:協程的本質是個單執行緒,它不能同時將

單個CPU 的多個核用上,協程需要和程序配合才能執行在多CPU.當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。

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

 

linux第三方庫安裝greenlet  gevent

pip3 install gevent

pip3 install greenlet 

greenlet(已經封裝好的協程的模組(手動切換協程)

gevent(已經封裝好的協程的模組(自動切換協程))

 

試驗greenlet

__author__ = "Burgess Zheng"

from greenlet import greenlet#greenlet 已經封裝好的協程
def test1():
    print(12)# 1.首先列印顯示:12
    gr2.switch()#切換(啟動)gr2該協程
    print(34)#3.列印顯示:34
    gr2.switch()#切換(啟動)gr2該協程
def test2():
    print(56)#2.列印顯示:56
    gr1.switch()#切換(啟動)gr1該協程
    print(78)#4.列印顯示:78

gr1 = greenlet(test1) #例項化一個協程
gr2 = greenlet(test2) #例項化一個攜程
gr1.switch()#切換(啟動)gr1該協程 (現在開始執行test1協程)

執行結果:

 

試驗Gevent

__author__ = "Burgess Zheng"

import gevent

def foo():
    print('Running in foo')     # 1
    gevent.sleep(2)
    print('Explicit context switch to foo again')#6
def bar():
    print('Explicit精確的 context內容 to bar')  #2
    gevent.sleep(1)
    print('Implicit context switch back to bar')#5
def func3():
    print("running func3 ")   #3
    gevent.sleep(0)
    print("running func3  again ")#4


gevent.joinall([         #一次性生成多個協程
    gevent.spawn(foo), #例項化生成一個自動協程
    gevent.spawn(bar),  #例項化生成一個自動協程
    gevent.spawn(func3), #例項化生成一個自動協程
])

執行結果:

試驗協程併發爬網頁 和 列表for呼叫同步序列爬網頁的區別和效率

 

__author__ = "Burgess Zheng"

from urllib import request#簡單爬蟲
import gevent,time
from gevent import monkey#因為gevent不知道urllib內部是否在進行IO操作,
# 所以需要我們需要讓gevent知道urllib在程序內部操作,就需要引入monkey
monkey.patch_all() #把當前程式的所有的io操作給我單獨的做上標記

def f(url):
    print('GET: %s' % url)
    resp = request.urlopen(url)#獲取該網址頁面的內容
    data = resp.read()
    #f = open("url.html","wb")#建立檔案url.html
    #f.write(data)#獲得網址內容寫入檔案
    #f.close()#完成以後關閉
    print('%d bytes received from %s.' % (len(data), url))

#使用列表迴圈操作爬蟲(同步序列)
urls = ['https://www.python.org/',
        'https://www.yahoo.com/',
        'https://github.com/' ]
time_start = time.time()#開始執行時間
for url in urls:
    f(url)
print("同步cost",time.time() - time_start)#當前時間減去開始執行時間=獲得執行時間


#使用協程爬蟲(效果是非同步並行)
async_time_start = time.time()#非同步開始執行時間
gevent.joinall([
    gevent.spawn(f, 'https://www.python.org/'),
    gevent.spawn(f, 'https://www.yahoo.com/'),
    gevent.spawn(f, 'https://github.com/'),
])
print("非同步cost",time.time() - async_time_start)
#當前時間減去非同步開始執行時間=獲得執行時間

執行結果:

 

socket協程高併發處理試驗(比Python自帶的socketserver(執行緒)還NB)

socketserver

__author__ = "Burgess Zheng"

import sys
import socket
import time
import gevent

from gevent import socket, monkey

monkey.patch_all()


def server(port):
    s = socket.socket()#啟動socket
    s.bind(('0.0.0.0', port))#繫結埠
    s.listen(500)#允許500個併發
    while True:
        cli, addr = s.accept()#接收到資料以後會返回2個值:連結標記位和對方的地址
        gevent.spawn(handle_request, cli)#啟動執行緒handle_request 連結標記位作為實參

#這樣每進來一個使用者訪問就啟動一個協程,就代表可以處理高併發

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)#關閉客戶端 ,用break也可以

    except Exception as  ex:#抓異常
        print(ex)#列印異常
    finally:#無論是否異常繼續執行下面的
        conn.close()#關閉連線


if __name__ == '__main__':
    server(8001)

socketclient

__author__ = "Burgess Zheng"
import socket

HOST = 'localhost'  # The remote host
PORT = 8001  # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
    msg = bytes(input(">>:"), encoding="utf8")
    s.sendall(msg)
    data = s.recv(1024)

    #
print('Received', repr(data))#repr格式輸出 也可以直接data ,沒鳥用
s.close()

執行結果: