1.協程概述
1.協程是python個中另外一種實作多任務的方式,只不過比執行緒更小占用更小執行單元,協程也稱作微執行緒,在本質只有一個執行緒在運行
2.在一個執行緒中的某個函式,可以在任何地方保存當前函式的一些臨時變數等資訊,然后切換到另外一個函式中執行
3.協程是通過生成器函式實作,函式間切換的次數以及什么時候再切換到原來的函式都由開發者自己確定
4.協程原理: 通過應用層記錄程式的背景關系堆疊區,實作程式運行中的跳躍,進而選擇代碼段執行
2.使用生成器函式實作協程
import time def work1(): while True: print("----work1---") yield time.sleep(0.5) def work2(): while True: print("----work2---") yield time.sleep(0.5) def main(): w1 = work1() w2 = work2() while True: next(w1) next(w2) if __name__ == "__main__": main() """執行結果 ----work1--- ----work2--- ----work1--- ----work2--- ----work1--- ----work2--- ... """
3.使用greenlet模塊實作協程
# greenlet模塊是對yield的封裝 # greenlet 只是可以實作一個簡單的切換功能,還是不能做到遇到IO就切換 # g1 = greenlet(func) # 實體化一個物件 # g1.switch() # 用這種方式去呼叫func函式 # 當使用switch呼叫func的時候,什么時候func會停止運行? # 1 要么return # 2 要么在func內部又遇到 switch from greenlet import greenlet import time def test1(): while True: print("---A--") gr2.switch() # 切換但gr2協程函式 time.sleep(0.5) def test2(): while True: print("---B--") gr1.switch() # 切換到gr1協程函式 time.sleep(0.5) # 生成協程物件 gr1 = greenlet(test1) gr2 = greenlet(test2) # 切換到gr1中運行 gr1.switch() """執行結果 ---A-- ---B-- ---A-- ---B-- ---A-- ---B-- ... """
4.使用gevent模塊實作協程
gevent實作協程原理
gevent模塊是對greenlet模塊的封裝,原理是當一個greenlet遇到IO(指的是input output 輸入輸出,比如網路,檔案操作等)操作時
例如訪問網路,就自動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行
示例
# gevent 可以實作當函式中遇到io操作時,就自動的切換到另一個函式 # g1 = gevent.spawn(func, 引數) # gevent.join(g1) # 讓func執行完畢 # gevent.joinall([g1, g2, g3, g4]) # func停止的原因: 1.func執行完了;2.遇到IO操作了 import gevent def f(n): for i in range(n): print(gevent.getcurrent(), i) # gevent.getcurrent()獲取當前協程物件 # 用來模擬一個耗時操作,此處不能是time模塊中的sleep gevent.sleep(1) # gevent.spawn(func, argv) 將func函式變成協程時間并啟動,回傳一個協程物件 # 引數1: func 事件函式 # 引數2: argv 多項,為func的引數 g1 = gevent.spawn(f, 3) g2 = gevent.spawn(f, 2) g3 = gevent.spawn(f, 3) # gevent.join(g1) # 等待回收協程,引數為要回收的協程物件 # gevent.joinall([g2, g3]) # 回收多個協程,引數為串列 g1.join() # 等待g1指向的任務執行結束 g2.join() # 通過協程物件的方法也可以回收協程 g3.join() """執行結果 <Greenlet at 0x10b63aef0: f(3)> 0 <Greenlet at 0x10b72b050: f(2)> 0 <Greenlet at 0x10b72b170: f(3)> 0 <Greenlet at 0x10b63aef0: f(3)> 1 <Greenlet at 0x10b72b050: f(2)> 1 <Greenlet at 0x10b72b170: f(3)> 1 <Greenlet at 0x10b63aef0: f(3)> 2 <Greenlet at 0x10b72b170: f(3)> 2 """
5.協程的最終實作方法: gevent + monkey
# 在實際開發中因為不確定那些地方的延時要替換成gevent中的延時,所以使用monkey給程式打補丁 from gevent import monkey import gevent import random import time # 有耗時操作時需要 monkey.patch_all() # 將程式中用到的耗時操作的代碼,換為gevent中自己實作的模塊 def coroutine_work(coroutine_name): for i in range(10): print(coroutine_name, i) # gevent.getcurrent()獲取當前協程物件 time.sleep(random.random()) gevent.joinall([ gevent.spawn(coroutine_work, "work1"), gevent.spawn(coroutine_work, "work2") ])
6.串行和協成并發效率對比
from gevent import monkey monkey.patch_all() import gevent import time def func1(num): time.sleep(1) print(num) # 串行執行 start = time.time() for i in range(10): func1(i) print(time.time() - start) # 10.00 # 協成并發執行 start = time.time() lst = [] for i in range(10): g = gevent.spawn(func1, i) lst.append(g) gevent.joinall(lst) print(time.time() - start) # 1.00
7.協程實作并發下載器
from gevent import monkey import gevent import urllib.request # 有耗時操作時需要 monkey.patch_all() def my_downLoad(url): print('GET: %s' % url) resp = urllib.request.urlopen(url) data = resp.read() with open(file_name, "wb") as f: f.write(data) print('%d bytes received from %s.' % (len(data), url)) gevent.joinall([ gevent.spawn(my_downLoad, 'http://www.baidu.com/'), gevent.spawn(my_downLoad, 'https://www.cnblogs.com/'), gevent.spawn(my_downLoad, 'https://www.cnblogs.com/tangxuecheng/') ])
8.協程并發-新浪圖集爬蟲
import urllib.request import gevent from gevent import monkey import re import os monkey.patch_all() IMG_URL = "http://slide.games.sina.com.cn/" def themes(img_utl): """爬取主題圖集分類""" req = urllib.request.urlopen(img_utl) req = str(req.read()) # 取主題正則 theme_re = r"<li> <a href=https://www.cnblogs.com/"(.*?)\" target=\"_blank\" class=\"game_hover\">" com = re.compile(theme_re) req_iterator = com.finditer(req) # 清洗資料-->迭代器的前兩個成員無效url,拿到前兩個值后直接丟棄 next(req_iterator) next(req_iterator) return req_iterator def pages(img_url, req_iterator): """爬取頁面圖集分類""" j = 1 for i_url in req_iterator: i_url = i_url.group(1) # print(i_url) pages_req = urllib.request.urlopen(i_url) pages_req = str(pages_req.read()) # 取頁碼正則 page_num_re = r"<!-- -->.*?href=https://www.cnblogs.com/".*?page=(\d+).*?\".*?<!-- -->" com = re.compile(page_num_re) page_num_iterator = com.finditer(pages_req) page_num = int(next(page_num_iterator).group(1)) # 取頁面url pages_re = r"</a><a style=.*?href=https://www.cnblogs.com/"(.*?)\" title=\".*?\">.*?<!-- -->" com = re.compile(pages_re) pages_iterator = com.finditer(pages_req) pages_url = img_url + next(pages_iterator).group(1) for i in range(1, page_num + 1): new_pages_url = pages_url + "&page=" + str(i) + "&dpc=1" pictures(new_pages_url, j, i) j += 1 def pictures(new_pages_url, j, i): """爬取分類中每頁的圖片""" req = urllib.request.urlopen(new_pages_url) req_str = str(req.read()) img_re = r"img src=https://www.cnblogs.com/"(.*?)\" class=" com = re.compile(img_re) req_str = com.findall(req_str) k = 1 for req_msg in req_str: i_msg = os.getcwd() + "/新浪圖集/" + "網站板塊" + str(j) + "/" + "網站第" + str(i) + "頁" + "/" + str(k) + ".jpg" if not os.path.exists(os.getcwd() + "/新浪圖集/" + "網站板塊" + str(j) + "/" + "網站第" + str(i) + "頁" + "/"): os.makedirs(os.getcwd() + "/新浪圖集/" + "網站板塊" + str(j) + "/" + "網站第" + str(i) + "頁" + "/") # 啟動協程-當出現延時時自動切換協程函式 gevent.joinall([ gevent.spawn(downloader, i_msg, req_msg) ]) k += 1 def downloader(i_msg, req_msg): """協程實作-并發下載""" req = urllib.request.urlopen(req_msg) img_content = req.read() with open(i_msg, "wb") as f: f.write(img_content) def main(): req_iterator = themes(IMG_URL) pages(IMG_URL, req_iterator) if __name__ == "__main__": main()
9.關于猴子補丁的使用和深坑
1.猴子補丁使用
from gevent import monkey
# 有耗時操作時需要,例如在匯入socket模塊前使用可以將socket模塊的IO設定為非阻塞
monkey.patch_all()
2.猴子補丁的深坑
1.匯入包的位置,補丁以下都會被改成阻塞
2.patch_all的引數默認值
patch_all(socket=True, dns=True, time=True, select=True, thread=True, os=True, ssl=True,
subprocess=True, sys=False, aggressive=True, Event=True,
builtins=True, signal=True,
queue=True, contextvars=True,
**kwargs)
3.開啟多執行緒時,monkey會阻塞主執行緒執
4.決絕方法: monkey.patch_all(thread=False)
10.協程通訊服務器
import gevent from gevent import monkey # 在匯入socket前執行,改變socket的阻塞狀態 monkey.patch_all() from socket import * from time import ctime def server(port): # 創建tcp流式套接字 tcp_server_socket = socket() # 設定埠重用 tcp_server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 系結地址 tcp_server_socket.bind(("0.0.0.0", port)) # 開啟監聽,創建監聽佇列 tcp_server_socket.listen(128) # 回圈為客戶端服務 print("服務器正常運行,等待客戶端連接") while True: client_socket, client_addr = tcp_server_socket.accept() print("客戶端%s已連接服務器" % str(client_addr)) gevent.spawn(handler, client_socket, client_addr) # 處理客戶端事件 def handler(client_socket, client_addr): while True: data = client_socket.recv(1024) if not data: break print("%s: %s" % (str(client_addr), data.decode())) client_socket.send(ctime().encode()) client_socket.close() def main(): # 運行服務器 server(7890) if __name__ == "__main__": main()
11.協程通訊客戶端
import socket def main(): # 創建資料流套接字 tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 連接服務器 server_ip = input("請輸入要連接的服務器的ip:") serve_port = int(input("請輸入要連接的服務器的port:")) server_addr = (server_ip, serve_port) tcp_client_socket.connect(server_addr) while True: # 發送資料 send_data = https://www.cnblogs.com/tangxuecheng/p/input("請輸入要發生的資料(quit退出):") if send_data.lower() == "quit": break tcp_client_socket.send(send_data.encode("utf-8")) # 接收服務器發送過來的資料 recv_data = https://www.cnblogs.com/tangxuecheng/p/tcp_client_socket.recv(1024) print("接收到的資料為:%s" % recv_data.decode("utf-8")) # 關閉套接字 tcp_client_socket.close() if __name__ == "__main__": main()
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/13998.html
標籤:Python
上一篇:圖片放大模糊怎么辦,Python無損清晰放大,360P變4K
下一篇:基于python常用排序與查找
