Python使用asyncio標(biāo)準(zhǔn)庫對異步IO的支持
Python中,所有程序的執(zhí)行都是單線程的,但可同時(shí)執(zhí)行多個(gè)任務(wù),不同的任務(wù)被時(shí)間循環(huán)(Event Loop)控制及調(diào)度,Asyncio是Python并發(fā)編程的一種實(shí)現(xiàn)方式;是Python 3.4版本引入的標(biāo)準(zhǔn)庫,直接內(nèi)置了對異步IO的支持。
異步IO
CPU的速度遠(yuǎn)遠(yuǎn)快于磁盤、網(wǎng)絡(luò)等IO。在一個(gè)線程中,CPU執(zhí)行代碼的速度極快,然而,一旦遇到IO操作,如讀寫文件、發(fā)送網(wǎng)絡(luò)數(shù)據(jù)時(shí),就需要等待IO操作完成,才能繼續(xù)進(jìn)行下一步操作。這種情況稱為同步IO。
在IO操作的過程中,當(dāng)前線程被掛起,而其他需要CPU執(zhí)行的代碼就無法被當(dāng)前線程執(zhí)行了。
因?yàn)橐粋€(gè)IO操作就阻塞了當(dāng)前線程,導(dǎo)致其他代碼無法執(zhí)行,所以我們必須使用多線程或者多進(jìn)程來并發(fā)執(zhí)行代碼,為多個(gè)用戶服務(wù)。每個(gè)用戶都會(huì)分配一個(gè)線程,如果遇到IO導(dǎo)致線程被掛起,其他用戶的線程不受影響。
多線程和多進(jìn)程的模型雖然解決了并發(fā)問題,但是系統(tǒng)不能無上限地增加線程。由于系統(tǒng)切換線程的開銷也很大,所以,一旦線程數(shù)量過多,CPU的時(shí)間就花在線程切換上了,真正運(yùn)行代碼的時(shí)間就少了,結(jié)果導(dǎo)致性能嚴(yán)重下降。
由于我們要解決的問題是CPU高速執(zhí)行能力和IO設(shè)備的龜速嚴(yán)重不匹配,多線程和多進(jìn)程只是解決這一問題的一種方法。
另一種解決IO問題的方法是異步IO。當(dāng)代碼需要執(zhí)行一個(gè)耗時(shí)的IO操作時(shí),它只發(fā)出IO指令,并不等待IO結(jié)果,然后就去執(zhí)行其他代碼了。一段時(shí)間后,當(dāng)IO返回結(jié)果時(shí),再通知CPU進(jìn)行處理。
asyncio
asyncio
是Python 3.4版本引入的標(biāo)準(zhǔn)庫,直接內(nèi)置了對異步IO的支持。
asyncio
的編程模型就是一個(gè)消息循環(huán)。我們從asyncio
模塊中直接獲取一個(gè)EventLoop
的引用,然后把需要執(zhí)行的協(xié)程扔到EventLoop
中執(zhí)行,就實(shí)現(xiàn)了異步IO。
用asyncio
實(shí)現(xiàn)Hello world
代碼如下:
import asyncio @asyncio.coroutine def hello(): print("Hello world!") # 異步調(diào)用asyncio.sleep(1): r = yield from asyncio.sleep(1) print("Hello again!") # 獲取EventLoop: loop = asyncio.get_event_loop() # 執(zhí)行coroutine loop.run_until_complete(hello()) loop.close()
@asyncio.coroutine
把一個(gè)generator標(biāo)記為coroutine類型,然后,我們就把這個(gè)coroutine
扔到EventLoop
中執(zhí)行。
hello()
會(huì)首先打印出Hello world!
,然后,yield from
語法可以讓我們方便地調(diào)用另一個(gè)generator
。由于asyncio.sleep()
也是一個(gè)coroutine
,所以線程不會(huì)等待asyncio.sleep()
,而是直接中斷并執(zhí)行下一個(gè)消息循環(huán)。當(dāng)asyncio.sleep()
返回時(shí),線程就可以從yield from
拿到返回值(此處是None
),然后接著執(zhí)行下一行語句。
把asyncio.sleep(1)
看成是一個(gè)耗時(shí)1秒的IO操作,在此期間,主線程并未等待,而是去執(zhí)行EventLoop
中其他可以執(zhí)行的coroutine
了,因此可以實(shí)現(xiàn)并發(fā)執(zhí)行。
我們用Task封裝兩個(gè)coroutine
試試:
import threading import asyncio @asyncio.coroutine def hello(): print('Hello world! (%s)' % threading.currentThread()) yield from asyncio.sleep(1) print('Hello again! (%s)' % threading.currentThread()) loop = asyncio.get_event_loop() tasks = [hello(), hello()] loop.run_until_complete(asyncio.wait(tasks)) loop.close()
觀察執(zhí)行過程:
Hello world! (<_MainThread(MainThread, started 140735195337472)>) Hello world! (<_MainThread(MainThread, started 140735195337472)>) (暫停約1秒) Hello again! (<_MainThread(MainThread, started 140735195337472)>) Hello again! (<_MainThread(MainThread, started 140735195337472)>)
由打印的當(dāng)前線程名稱可以看出,兩個(gè)coroutine
是由同一個(gè)線程并發(fā)執(zhí)行的。
如果把asyncio.sleep()
換成真正的IO操作,則多個(gè)coroutine
就可以由一個(gè)線程并發(fā)執(zhí)行。
我們用asyncio
的異步網(wǎng)絡(luò)連接來獲取sina、sohu和163的網(wǎng)站首頁:
import asyncio @asyncio.coroutine def wget(host): print('wget %s...' % host) connect = asyncio.open_connection(host, 80) reader, writer = yield from connect header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host writer.write(header.encode('utf-8')) yield from writer.drain() while True: line = yield from reader.readline() if line == b'\r\n': break print('%s header > %s' % (host, line.decode('utf-8').rstrip())) # Ignore the body, close the socket writer.close() loop = asyncio.get_event_loop() tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']] loop.run_until_complete(asyncio.wait(tasks)) loop.close()
執(zhí)行結(jié)果如下:
wget www.sohu.com... wget www.sina.com.cn... wget www.163.com... (等待一段時(shí)間) (打印出sohu的header) www.sohu.com header > HTTP/1.1 200 OK www.sohu.com header > Content-Type: text/html ... (打印出sina的header) www.sina.com.cn header > HTTP/1.1 200 OK www.sina.com.cn header > Date: Wed, 20 May 2015 04:56:33 GMT ... (打印出163的header) www.163.com header > HTTP/1.0 302 Moved Temporarily www.163.com header > Server: Cdn Cache Server V2.0 ...
可見3個(gè)連接由一個(gè)線程通過coroutine
并發(fā)完成。
小結(jié)
asyncio
提供了完善的異步IO支持;異步操作需要在coroutine
中通過yield from
完成;多個(gè)coroutine
可以封裝成一組Task然后并發(fā)執(zhí)行。
到此這篇關(guān)于Python使用asyncio標(biāo)準(zhǔn)庫對異步IO的支持的文章就介紹到這了,更多相關(guān)Python使用asyncio內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Python使用asyncio實(shí)現(xiàn)異步操作的示例
- Python中asyncio的多種用法舉例(異步同步)
- Python使用asyncio處理異步編程的代碼示例
- Python使用asyncio包實(shí)現(xiàn)異步編程方式
- Python異步庫asyncio、aiohttp詳解
- python協(xié)程異步IO中asyncio的使用
- Python協(xié)程異步爬取數(shù)據(jù)(asyncio+aiohttp)實(shí)例
- Python使用asyncio異步時(shí)的常見問題總結(jié)
- Python asyncio異步編程常見問題小結(jié)
- Python asyncio異步編程簡單實(shí)現(xiàn)示例
- Python中asyncio庫實(shí)現(xiàn)異步編程的示例
相關(guān)文章
詳解Pandas中stack()和unstack()的使用技巧
當(dāng)你在處理包含某種序列(例如時(shí)間序列數(shù)據(jù))的變量的數(shù)據(jù)集時(shí),數(shù)據(jù)通常需要進(jìn)行重塑。Pandas?提供了各種用于重塑?DataFrame?的內(nèi)置方法。其中,stack()?和?unstack()?是最流行的,本文總結(jié)了這兩個(gè)方法的7種使用技巧,需要的可以參考一下2022-03-03python畫圖把時(shí)間作為橫坐標(biāo)的方法
今天小編就為大家分享一篇python畫圖把時(shí)間作為橫坐標(biāo)的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-07-07Python基礎(chǔ)之字典常見操作經(jīng)典實(shí)例詳解
這篇文章主要介紹了Python基礎(chǔ)之字典常見操作,結(jié)合實(shí)例形式詳細(xì)分析了Python基本功能、創(chuàng)建、內(nèi)置函數(shù)與相關(guān)使用技巧,需要的朋友可以參考下2020-02-02Python實(shí)現(xiàn)AI自動(dòng)摳圖實(shí)例解析
這篇文章主要介紹了Python實(shí)現(xiàn)AI自動(dòng)摳圖實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03Python 使用 Pillow 模塊給圖片添加文字水印的方法
這篇文章主要介紹了Python 使用 Pillow 模塊給圖片添加文字水印的方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08