Python:通过一个小案例深入理解IO多路复用
通過一個小案例深入理解IO多路復用
假如我們現在有這樣一個普通的需求,寫一個簡單的爬蟲來爬取?;ňW的主頁
import requests
import timestart = time.time()url = 'http://www.xiaohuar.com/'
result = requests.get(url).textprint(result)
print(time.time()-start)
這樣子是顯然沒啥問題的,總共耗時約為6秒
?
但是有沒有辦法更進一步優化呢,這里如果需要優化我們首先需要知道一個知識點
就是requests這個模塊它底層其實是封裝了urllib2和urllib3的,而這兩個模塊底層其實就是socket
如果需要優化,從requests是實現不了的,那么能不能從socket來呢
如果從socket,又該如何優化呢?
?
首先我們得知道socket到底做了什么,
import socketclient = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
url = 'www.xiaohuar.com/'
client.connect((url, 80))
client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format('/',80).encode('utf8'))
data = b''
while 1:d = client.recv(1024)if d:data +=delse:breakprint(data)
這里的代碼就是上面那個requests版本的代碼的底層
在這一坨代碼中,有幾個點需要注意
connect和recv,這兩個方法都是阻塞io,也就是說,如果連接不到或者接受不到消息的話,程序就會一直等,等到預期的效果為止。
這就是阻塞
?
阻塞有個很大的弊端,那就是cpu無法得到充分利用,因為等待的時間里,cpu是空閑的,而我們又沒有執行其他的操作,那么這段時間我們能不能充分利用起來呢
答案是肯定的,socket提供了一個非阻塞的辦法
client.setblocking(False)
直接運行試試效果
BlockingIOError: [WinError 10035] 無法立即完成一個非阻止性套接字操作。
結果是拋出了這個異常,這是因為當變為非阻塞時候,連接?;ňW的url的時候,三次握手還沒建立完成,我們就去執行下一步了
try:client.connect((url, 80))
except BlockingIOError as e:
#處理其他事情pass
那么我們可以這樣改,抓到這個異常但是不處理,這樣子,我們就能在except后面加入其他的代碼了,也就是說cpu發個請求就不管了,然后去執行后面的代碼,這樣效率就提高了。
再運行一次。
OSError: [WinError 10057] 由于套接字沒有連接并且(當使用一個 sendto 調用發送數據報套接字時)沒有提供地址,發送或接收數據的請求沒有被接受。
又拋出了一個異常,和上面的原理差不多,因為是非阻塞模式
最終代碼如下
import socketclient = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.setblocking(False)
url = 'www.xiaohuar.com'
try:client.connect((url, 80))
except BlockingIOError as e:passwhile 1:try:client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format('/',80).encode('utf8'))breakexcept Exception as e:passdata = b''
while 1:try:d = client.recv(1024)except Exception as e:continueif d:data += delse:breakprint(data)
這樣子雖然有一段時間更充分利用了cpu 但是代碼很亂,很麻煩,其次雖然是非阻塞,但是有兩個地方只是把之前的阻塞的時間花費了在循環上,那么有沒有更好的辦法呢?
?
這里就要引入IO多路復用的概念了
IO復用就是通過一種機制,一個進程可以監視多個描述符,一旦某個描述符就緒(讀或者寫),都能夠通知程序來進行相應的讀寫操作,但是select,poll和epoll都是同步io,也就是說這個讀寫過程是阻塞的,而異步io則無需自己進行讀寫,異步io的實現會負責把數據從內核拷貝到用戶內存。
?
select在windows,OS X, 或者linux都能用,但是select最大監視數量只能為1024
而poll的話其他幾乎與select一樣,只是突破了最大限制
而epoll就與前面這兩個都不一樣了,它底層使用了紅黑樹的數據結構,epoll使用一個文件描述符來管理多個文件描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表之中,這樣在用戶空間和內核空間的copy只需一次。
而poll和select都是才用輪詢的方式,所以效率差就在這里體現出來了
?
最終代碼 異步IO
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE
import socketselector = DefaultSelector()class Fetcher():def send_msg(self, key):selector.unregister(key.fd)self.client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format('/', 80).encode('utf8'))selector.register(self.client.fileno(), EVENT_READ, self.recv)def recv(self, key):d = self.client.recv(1024)if d:self.data += delse:selector.unregister(key.fd)print(self.data.decode('utf8'))def get_url(self, url):self.data = b''try:self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.client.connect((url, 80))except Exception as e:# 加入另外的邏輯passselector.register(self.client.fileno(), EVENT_WRITE, self.send_msg)def loop_forever():while 1:ready = selector.select()for key, mask in ready:call_back = key.datacall_back(key)if __name__ == '__main__':fet = Fetcher()fet.get_url('www.xiaohuar.com')loop_forever()
?
轉載于:https://www.cnblogs.com/Miracle-boy/p/10004684.html
總結
以上是生活随笔為你收集整理的Python:通过一个小案例深入理解IO多路复用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么食物能止咳化痰?
- 下一篇: 如何创建systemd定时任务