Python實戰之協程(greenlet模組,gevent模組,socket+ gevent實現高併發處理)
協程
協程,又稱微執行緒,纖程。英文名Coroutine。一句話說明什麼是執行緒:協程是一種使用者態的輕量級執行緒。(cpu不知道,是使用者自己控制的)
協程擁有自己的暫存器上下文和棧。協程排程切換時,將暫存器上下文和棧儲存到其他地方,在切回來的時候,恢復先前儲存的暫存器上下文和棧(執行緒的上下文切換是儲存在CPU,協程就不是)。因此:
協程能保留上一次呼叫時的狀態(即所有區域性狀態的一個特定組合),每次過程重入時,就相當於進入上一次呼叫的狀態,換種說法:進入上一次離開時所處邏輯流的位置(yield生產消費就是協程)。
協程的好處:
無需執行緒上下文切換的開銷
無需原子操作鎖定及同步的開銷
"原子操作(atomic operation)是不需要synchronized",所謂原子操作是指不會被執行緒排程機制打斷的操作;這種操作一旦開始,就一直執行到結束,中間不會有任何 context switch (切換到另一個執行緒)。原子操作可以是一個步驟,也可以是多個操作步驟,但是其順序是不可以被打亂,或者切割掉只執行部分。視作整體是原子性的核心。
方便切換控制流,簡化程式設計模型
高併發+高擴充套件性+低成本:一個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()
執行結果: