pythonasyncio在哪个版本好_理解Python asyncio的简洁方式
異步IO是個好東西,在網(wǎng)絡讀寫場景中可以大大提高程序的并發(fā)能力,比如爬蟲、web服務等。這樣的好東西自然也要在Python中可以使用。不過,在漫長的Python2時代,官方并沒有推出一個自己的異步IO庫,到了Python 3.4 才推出。我們先來看看異步IO在Python中的發(fā)展歷史。
Python 異步IO的歷史
Python 2的異步IO庫
Python 2 時代官方并沒有異步IO的支持,但是有幾個第三方庫通過事件或事件循環(huán)(Event Loop)實現(xiàn)了異步IO,它們是:twisted: 是事件驅(qū)動的網(wǎng)絡庫
gevent: greenlet + libevent(后來是libev或libuv)。通過協(xié)程(greenlet)和事件循環(huán)庫(libev,libuv)實現(xiàn)的gevent使用很廣泛。
tornado: 支持異步IO的web框架。自己實現(xiàn)了IOLOOP。
Python 3 官方的異步IO
Python 3.4 加入了asyncio 庫,使得Python有了支持異步IO的官方庫。這個庫,底層是事件循環(huán)(EventLoop),上層是協(xié)程和任務。asyncio自從3.4 版本加入到最新的 3.7版一直在改進中。Python 3.4 剛開始的asyncio的協(xié)程還是基于生成器的,通過 yield from 語法實現(xiàn),可以通過裝飾器?@asyncio.coroutine(已過時)裝飾一個函數(shù)來定義一個協(xié)程。比如:
Python 3.5 引入了兩個新的關鍵字 await 和 async 用來替換 @asyncio.coroutine 和 yield from ,從語言本身來支持異步IO。從而使得異步編程更加簡潔,并和普通的生成器區(qū)別開來。注意:對基于生成器的協(xié)程的支持已棄用,并計劃在 Python 3.10 中移除。所以,寫異步IO程序時只需使用 async 和 await 即可。Python 3.7 又進行了優(yōu)化,把API分組為高層級API和低層級API。我們先看看下面的代碼,發(fā)現(xiàn)與上面的有什么不同?除了用 async 替換 @asyncio.coroutine 和用 await 替換 yield from 外,最大的變化就是關于eventloop的代碼不見了,只有一個 async.run()。這就是 3.7 的改進,把eventloop相關的API歸入到低層級API,新引進run()作為高層級API讓寫應用程序的開發(fā)者調(diào)用,而不用再關心eventloop。除非你要寫異步庫(比如MySQL異步庫)才會和eventloop打交道。
理解asyncio
理解asyncio并不能,關鍵是要動起手來,接下來我們以下面代碼為例動手實踐一番,通過實踐來理解它。
這段代碼很簡單,我們定義了兩個協(xié)程函數(shù)(在def前面加async),其中 hi()?我們把它叫做功能函數(shù),通過一個 aysncio.sleep() 來模擬一個耗時的異步IO操作(比如下載網(wǎng)頁), main() 叫做入口函數(shù)。其實就是在main() 里面調(diào)用 hi() 函數(shù),通過不斷改變 main() 的行為來理解異步IO(協(xié)程函數(shù)的調(diào)用)的運行過程。
1. 協(xié)程函數(shù)如何運行?
首先,我們要明確一個道理,hi() 是一個協(xié)程函數(shù),直接調(diào)用它返回的是一個協(xié)程對象,并沒有真正運行它。把main函數(shù)改成如下,我們來仔細看看協(xié)程函數(shù) hi() 的運行。
下面是運行結果:
代碼第19行,我們像運行普通函數(shù)一樣運行 hi() ,得到的a只是一個協(xié)程對象,見結果第二行:
a is: 這個協(xié)程對象 a 雖然生成了,但是還沒有運行,它需要一個時機。也就是asyncio的事件循環(huán)正在運行main,還沒有空去運行它。
代碼第21行,通過 await 告訴 event_loop(事件循環(huán))?,main協(xié)程停在這里,你去運行其它協(xié)程吧。這時候 event_loop 去執(zhí)行a協(xié)程,也就是去執(zhí)行 hi() 函數(shù)里面的代碼。等 hi() 運行完,event_loop 再回到main協(xié)程繼續(xù)從21行開始執(zhí)行,把 hi() 的返回值賦值給b,這時候 b 的值是1。
event_loop 在整個異步IO過程中扮演一個管家的角色,在不同的協(xié)程之間切換運行代碼,切換是通過事件來進行的,通過 await 離開當前協(xié)程,await 的協(xié)程完成后又回到之前的協(xié)程對應的地方繼續(xù)執(zhí)行。
2. 協(xié)程函數(shù)如何并發(fā)?
異步IO的好處就是并發(fā),但如何實現(xiàn)呢?我們先來看一個不是并發(fā)的例子:
這次,我們把main修改成一個for循環(huán)執(zhí)行4次 hi()?,看看它運行的結果:
整個過程從21:48:30 到 21:48:40 結束,用了10秒。而hi()的執(zhí)行時間分別是1秒,2秒,3秒,4秒總共10秒。也就是4個hi() 雖然是異步的但是順序執(zhí)行的,沒有并發(fā)。
接下來,就到了并發(fā)的實現(xiàn)了,通過 asyncio.creat_task()?即可:
通過 create_task() 我們在for循環(huán)里面生成了4個task(也是協(xié)程對象),但是這4個協(xié)程任務并沒有被執(zhí)行,它們需要等待一個時機:當前協(xié)程(main)遇到 await。
第二個for循環(huán)開始逐一 await 協(xié)程,此時 event_loop 就可以空出手來去執(zhí)行那4個協(xié)程,過程大致如下:先執(zhí)行hi(1, 1) ,打印“enter hi(), 1 @21:58:35”,遇到await asyncio.sleep(1),當前協(xié)程掛起;
接著執(zhí)行 hi(2, 2),執(zhí)行打印命令,遇到await asyncio.sleep(2)?,當前協(xié)程掛起;
接著執(zhí)行 hi(3, 3),執(zhí)行打印命令,遇到await asyncio.sleep(3)?,當前協(xié)程掛起;
接著執(zhí)行 hi(4, 4),執(zhí)行打印命令,遇到await asyncio.sleep(4)?,當前協(xié)程掛起;
以上4步只是協(xié)程的切換和打印語句,執(zhí)行非常快,我們可以任務它們是同時執(zhí)行起來的。
1秒后,hi(1,1)的sleep結束它會發(fā)出事件告訴 event_loop 我await結束了,過來執(zhí)行我,event_loop 此時空閑就來執(zhí)行它,繼續(xù)執(zhí)行sleep后面的打印語句;
2秒后,hi(2,2)的sleep結束它會發(fā)出事件告訴 event_loop 我await結束了,過來執(zhí)行我,event_loop 此時空閑就來執(zhí)行它,繼續(xù)執(zhí)行sleep后面的打印語句;
3秒后,hi(3,3)的sleep結束它會發(fā)出事件告訴 event_loop 我await結束了,過來執(zhí)行我,event_loop 此時空閑就來執(zhí)行它,繼續(xù)執(zhí)行sleep后面的打印語句;
4秒后,hi(4,4)的sleep結束它會發(fā)出事件告訴 event_loop 我await結束了,過來執(zhí)行我,event_loop 此時空閑就來執(zhí)行它,繼續(xù)執(zhí)行sleep后面的打印語句;
4秒后,生成的4個協(xié)程任務就都執(zhí)行完畢。總耗時4秒,也就是我們的4個任務并發(fā)完成了。
所以,上面的代碼運行的結果如下:
根據(jù)上面講述的執(zhí)行流程,可以看到結果對應起來了。4個任務都是在35秒時開始執(zhí)行,以后每個1秒完成一個。main函數(shù)從35執(zhí)行到39介紹,共耗時4秒。
3. 錯誤的運行
上面的并發(fā)很完美,但有時候你可能會犯錯。比如下面的main(), 你可能只是并發(fā) hi() 函數(shù),但不需要它的返回結果,于是有了下面的 main():
先猜猜會有什么樣的結果!!
你猜對了嗎?下面是運行結果:main()的for循環(huán)只是生成了4個task協(xié)程,然后就退出了。event_loop 收到main退出的事件就空出來去執(zhí)行了那4個協(xié)程,進去了但都碰到了sleep。然后event_loop就空閑了。這時候run() 就收到了main() 執(zhí)行完畢的事件,run() 就執(zhí)行完了,最后執(zhí)行print,整個程序就退出了。從main退出到整個程序退出就是一瞬間的事情,那4個協(xié)程還在傻傻的睡著,不,是在睡夢中死去了。
在main()中加一個sleep會出現(xiàn)什么結果:
在main()退出前,我們要先sleep 2秒,再來猜猜它的運行結果是什么?
如果你對上面沒有sleep的過程搞清楚了,不難猜到正確的結果:
注意:main() 的退出和 hi(2, 2) 的退出順序。簡單講,main() 先sleep 2秒,hi(2, 2) 后sleep兩秒,所以main先退出。
理解了sleep(2) 的執(zhí)行過程,那么你就可以知道 sleep(4) 和 sleep(5) 的結果了。如果沒有自信的話,就自己改一下時間,運行看看結果。
4. 如何判斷是否要把函數(shù)定義為協(xié)程函數(shù)?
定義一個協(xié)程函數(shù)很簡單,在def前面加async即可。那么如何判斷一個函數(shù)該不該定義為協(xié)程函數(shù)呢?
記住這一個原則:如果該函數(shù)是要進行IO操作(讀寫網(wǎng)絡、讀寫文件、讀寫數(shù)據(jù)庫等),就把它定義為協(xié)程函數(shù),否則就是普通函數(shù)。
《新程序員》:云原生和全面數(shù)字化實踐50位技術專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的pythonasyncio在哪个版本好_理解Python asyncio的简洁方式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java客户端传递参数_java –
- 下一篇: java完整版记事本_求java记事本完