greenlet和gevent模塊
阿新 • • 發佈:2018-06-03
標記 soc == 死鎖 無需 host 本質 back down
協程是用戶態的線程,並非真正意義上的線程,
協程只有一個線程,看起來並發的效果是因為它利用了寄存器的上下文切換,
多線程和多進程比較消耗cpu資源,當遇到修改數據的時候,還會遇到死鎖的問題。
協程是最大的發揮了cpu的單核能力,遇到io阻塞就切換,阻塞完成之後切換回來。
協程的好處:
- 跨平臺
- 跨體系架構
- 無需線程上下文切換的開銷
- 無需原子操作鎖定及同步的開銷
- 方便切換控制流,簡化編程模型
- 高並發+高擴展性+低成本:一個CPU支持上萬的協程都不是問題。所以很適合用於高並發處理。
缺點:
- 無法利用多核資源:協程的本質是個單線程,它不能同時將單個CPU 的多個核用上,協程需要和進程配合才能運行在多CPU上.當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
- 進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程序:這一點和事件驅動一樣,可以使用異步IO操作來解決
yield生成器實現協程的並發效果:
import time """ 協程:是用戶態的線程 yield單線程下實現並發效果 """ #生產者 def producer(name): con1.__next__() con2.__next__() count = 0 while count < 5: time.sleep(1) print("\033[31;1m[producer %s]\033[0m is making baozi..." % name) con1.send(count) con2.send(count) count += 1 #消費者 def consumer(name): print("準備開始吃包子...") while True: baozi = yield print("[%s]吃了包子[%s]" % (name, baozi)) if __name__ == ‘__main__‘: con1 = consumer("軒軒") con2 = consumer("壯壯") producer("小白")
greenlet代碼示例:
from greenlet import greenlet def homepage(): print("34") gr2.switch() print("12") gr3.switch() def bbs(): print("84") gr3.switch() print("13") def login(): print("56") gr1.switch() print("--end--") gr1 = greenlet(homepage) gr2 = greenlet(bbs) gr3 = greenlet(login)
執行結果:
34
84
56
12
--end--
gevent模塊是greentlet模塊的簡單化:
import gevent """ 自動檔的協程 自上而下從0開始計數,如果計數不大於sleep數,則跳過後面代碼 否則,執行後面代碼 """ def run1(): print("run1->1") gevent.sleep(2) print("run1->2") def run2(): print("run2->1") gevent.sleep(3) print("run2->2") def run3(): print("run3->1") gevent.sleep(1) print("run3->2") def run4(): print("run4->1") gevent.sleep(0) print("run4->2") #由上而下依次執行 gevent.joinall([ gevent.spawn(run1), gevent.spawn(run2), gevent.spawn(run3), gevent.spawn(run4), ])
執行結果:
run1->1
run2->1
run3->1
run4->1
run4->2
run3->2
run1->2
run2->2
利用協程實現並發爬取網頁內容:
import gevent, time from gevent import monkey monkey.patch_all() from urllib import request """ 利用協程,高效爬取網頁 gevent模塊默認不識別urllib的IO操作, 需要導入monkey給urllib裏面的操作打上標記 """ filename = "result.html" #爬取網頁內容到本地 def get_url_content(url): print("GET: %s" % url) res = request.urlopen(url) html = res.read() with open(filename, "wb") as f: f.write(html) print("%d bytes received from %s" % (len(html), url)) print("----------------------") url_list = [ "https://www.python.org/", "https://github.com/", "https://www.bilibili.com/" ] #啟動時間 serial_start_time = time.time() #串行的方式獲取網頁內容 for url in url_list: get_url_content(url) #計算花費時間 print("serial cost:", time.time() - serial_start_time) #啟動時間 async_start_time = time.time() #並行的方式獲取網頁內容 gevent.joinall([ gevent.spawn(get_url_content, "https://www.python.org/"), gevent.spawn(get_url_content, "https://github.com"), gevent.spawn(get_url_content, "https://www.bilibili.com/"), ]) #計算花費時間 print("async cost:", time.time() - async_start_time)
協程實現高並發socket服務器:
服務端:
import gevent from gevent import monkey monkey.patch_all() import socket """ 協程實現socket的高並發服務器 """ def handle_data(conn): try: while True: data = conn.recv(1024) if not data: conn.shutdown(socket.SHUT_WR) conn.send(data.upper()) except EXception as ex: print(ex) finally: conn.close() def my_server(port): server = socket.socket() #綁定地址和端口 server.bind(("0.0.0.0", port)) #開始監聽 server.listen(500) while True: #阻塞等待連接 conn, addr = server.accept() #來數據了 gevent.spawn(handle_data, conn) if __name__ == ‘__main__‘: my_server(6666)
客戶端:
import socket """ 協程實現socket高並發的客戶端 """ client = socket.socket() client.connect(("localhost", 6666)) while True: input_data = input(">>:").strip() if not input_data: continue client.send(input_data.encode()) data = client.recv(1024).decode() print(data) client.close()
greenlet和gevent模塊