日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 人文社科 > 生活经验 >内容正文

生活经验

Python:通过一个小案例深入理解IO多路复用

發(fā)布時(shí)間:2023/11/27 生活经验 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Python:通过一个小案例深入理解IO多路复用 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

通過(guò)一個(gè)小案例深入理解IO多路復(fù)用

假如我們現(xiàn)在有這樣一個(gè)普通的需求,寫一個(gè)簡(jiǎn)單的爬蟲來(lái)爬取?;ňW(wǎng)的主頁(yè)

import requests
import timestart = time.time()url = 'http://www.xiaohuar.com/'
result = requests.get(url).textprint(result)
print(time.time()-start)

  

  這樣子是顯然沒(méi)啥問(wèn)題的,總共耗時(shí)約為6秒

?

但是有沒(méi)有辦法更進(jìn)一步優(yōu)化呢,這里如果需要優(yōu)化我們首先需要知道一個(gè)知識(shí)點(diǎn)

就是requests這個(gè)模塊它底層其實(shí)是封裝了urllib2和urllib3的,而這兩個(gè)模塊底層其實(shí)就是socket

如果需要優(yōu)化,從requests是實(shí)現(xiàn)不了的,那么能不能從socket來(lái)呢

如果從socket,又該如何優(yōu)化呢?

?

首先我們得知道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)

  這里的代碼就是上面那個(gè)requests版本的代碼的底層

  在這一坨代碼中,有幾個(gè)點(diǎn)需要注意

  connect和recv,這兩個(gè)方法都是阻塞io,也就是說(shuō),如果連接不到或者接受不到消息的話,程序就會(huì)一直等,等到預(yù)期的效果為止。

  這就是阻塞

?

  阻塞有個(gè)很大的弊端,那就是cpu無(wú)法得到充分利用,因?yàn)榈却臅r(shí)間里,cpu是空閑的,而我們又沒(méi)有執(zhí)行其他的操作,那么這段時(shí)間我們能不能充分利用起來(lái)呢

  答案是肯定的,socket提供了一個(gè)非阻塞的辦法

  

client.setblocking(False)

  直接運(yùn)行試試效果

  BlockingIOError: [WinError 10035] 無(wú)法立即完成一個(gè)非阻止性套接字操作。

  結(jié)果是拋出了這個(gè)異常,這是因?yàn)楫?dāng)變?yōu)榉亲枞麜r(shí)候,連接?;ňW(wǎng)的url的時(shí)候,三次握手還沒(méi)建立完成,我們就去執(zhí)行下一步了

  

try:client.connect((url, 80))
except BlockingIOError as e:
  #處理其他事情pass

  那么我們可以這樣改,抓到這個(gè)異常但是不處理,這樣子,我們就能在except后面加入其他的代碼了,也就是說(shuō)cpu發(fā)個(gè)請(qǐng)求就不管了,然后去執(zhí)行后面的代碼,這樣效率就提高了。

  再運(yùn)行一次。

  OSError: [WinError 10057] 由于套接字沒(méi)有連接并且(當(dāng)使用一個(gè) sendto 調(diào)用發(fā)送數(shù)據(jù)報(bào)套接字時(shí))沒(méi)有提供地址,發(fā)送或接收數(shù)據(jù)的請(qǐng)求沒(méi)有被接受。

  又拋出了一個(gè)異常,和上面的原理差不多,因?yàn)槭欠亲枞J?/p>

最終代碼如下

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)

  這樣子雖然有一段時(shí)間更充分利用了cpu 但是代碼很亂,很麻煩,其次雖然是非阻塞,但是有兩個(gè)地方只是把之前的阻塞的時(shí)間花費(fèi)了在循環(huán)上,那么有沒(méi)有更好的辦法呢?

?

這里就要引入IO多路復(fù)用的概念了

IO復(fù)用就是通過(guò)一種機(jī)制,一個(gè)進(jìn)程可以監(jiān)視多個(gè)描述符,一旦某個(gè)描述符就緒(讀或者寫),都能夠通知程序來(lái)進(jìn)行相應(yīng)的讀寫操作,但是select,poll和epoll都是同步io,也就是說(shuō)這個(gè)讀寫過(guò)程是阻塞的,而異步io則無(wú)需自己進(jìn)行讀寫,異步io的實(shí)現(xiàn)會(huì)負(fù)責(zé)把數(shù)據(jù)從內(nèi)核拷貝到用戶內(nèi)存。

?

select在windows,OS X, 或者linux都能用,但是select最大監(jiān)視數(shù)量只能為1024

而poll的話其他幾乎與select一樣,只是突破了最大限制

而epoll就與前面這兩個(gè)都不一樣了,它底層使用了紅黑樹的數(shù)據(jù)結(jié)構(gòu),epoll使用一個(gè)文件描述符來(lái)管理多個(gè)文件描述符,將用戶關(guān)系的文件描述符的事件存放到內(nèi)核的一個(gè)事件表之中,這樣在用戶空間和內(nèi)核空間的copy只需一次。

而poll和select都是才用輪詢的方式,所以效率差就在這里體現(xiàn)出來(lái)了

?

最終代碼 異步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()

  

?

轉(zhuǎn)載于:https://www.cnblogs.com/Miracle-boy/p/10004684.html

總結(jié)

以上是生活随笔為你收集整理的Python:通过一个小案例深入理解IO多路复用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。