from time import sleep, time
def demo1():
"""
假設(shè)我們有三臺(tái)洗衣機(jī), 現(xiàn)在有三批衣服需要分別放到這三臺(tái)洗衣機(jī)里面洗.
"""
def washing1():
sleep(3) # 第一臺(tái)洗衣機(jī), 需要洗3秒才能洗完 (只是打個(gè)比方)
print('washer1 finished') # 洗完的時(shí)候, 洗衣機(jī)會(huì)響一下, 告訴我們洗完了
def washing2():
sleep(2)
print('washer2 finished')
def washing3():
sleep(5)
print('washer3 finished')
washing1()
washing2()
washing3()
"""
這個(gè)還是很容易理解的, 運(yùn)行 demo1(), 那么需要10秒鐘才能把全部衣服洗完.
沒(méi)錯(cuò), 大部分時(shí)間都花在挨個(gè)地等洗衣機(jī)上了.
"""
def demo2():
"""
現(xiàn)在我們想要避免無(wú)謂的等待, 為了提高效率, 我們將使用 async.
washing1/2/3() 本是 "普通函數(shù)", 現(xiàn)在我們用 async 把它們升級(jí)為 "異步函數(shù)".
注: 一個(gè)異步的函數(shù), 有個(gè)更標(biāo)準(zhǔn)的稱呼, 我們叫它 "協(xié)程" (coroutine).
"""
async def washing1():
sleep(3)
print('washer1 finished')
async def washing2():
sleep(2)
print('washer2 finished')
async def washing3():
sleep(5)
print('washer3 finished')
washing1()
washing2()
washing3()
"""
從正常人的理解來(lái)看, 我們現(xiàn)在有了異步函數(shù), 但是卻忘了定義應(yīng)該什么時(shí)候 "離開" 一臺(tái)洗衣
機(jī), 去看看另一個(gè)... 這就會(huì)導(dǎo)致, 現(xiàn)在的情況是我們一邊看著第一臺(tái)洗衣機(jī), 一邊著急地想著
"是不是該去開第二臺(tái)洗衣機(jī)了呢?" 但又不敢去 (只是打個(gè)比方), 最終還是花了10秒的時(shí)間才
把衣服洗完.
PS: 其實(shí) demo2() 是無(wú)法運(yùn)行的, Python 會(huì)直接警告你:
RuntimeWarning: coroutine 'demo2.<locals>.washing1' was never awaited
RuntimeWarning: coroutine 'demo2.<locals>.washing2' was never awaited
RuntimeWarning: coroutine 'demo2.<locals>.washing3' was never awaited
"""
def demo3():
"""
現(xiàn)在我們吸取了上次的教訓(xùn), 告訴自己洗衣服的過(guò)程是 "可等待的" (awaitable), 在它開始洗衣服
的時(shí)候, 我們可以去弄?jiǎng)e的機(jī)器.
"""
async def washing1():
await sleep(3) # 注意這里加入了 await
print('washer1 finished')
async def washing2():
await sleep(2)
print('washer2 finished')
async def washing3():
await sleep(5)
print('washer3 finished')
washing1()
washing2()
washing3()
"""
嘗試運(yùn)行一下, 我們會(huì)發(fā)現(xiàn)還是會(huì)報(bào)錯(cuò) (報(bào)錯(cuò)內(nèi)容和 demo2 一樣). 這里我說(shuō)一下原因, 以及在
demo4 中會(huì)給出一個(gè)最終答案:
1. 第一個(gè)問(wèn)題是, await 后面必須跟一個(gè) awaitable 類型或者具有 __await__ 屬性的
對(duì)象. 這個(gè) awaitable, 并不是我們認(rèn)為 sleep() 是 awaitable 就可以 await 了,
常見的 awaitable 對(duì)象應(yīng)該是:
await asyncio.sleep(3) # asyncio 庫(kù)的 sleep() 機(jī)制與 time.sleep() 不
# 同, 前者是 "假性睡眠", 后者是會(huì)導(dǎo)致線程阻塞的 "真性睡眠"
await an_async_function() # 一個(gè)異步的函數(shù), 也是可等待的對(duì)象
以下是不可等待的:
await time.sleep(3)
x = await 'hello' # <class 'str'> doesn't define '__await__'
x = await 3 + 2 # <class 'int'> dosen't define '__await__'
x = await None # ...
x = await a_sync_function() # 普通的函數(shù), 是不可等待的
2. 第二個(gè)問(wèn)題是, 如果我們要執(zhí)行異步函數(shù), 不能用這樣的調(diào)用方法:
washing1()
washing2()
washing3()
而應(yīng)該用 asyncio 庫(kù)中的事件循環(huán)機(jī)制來(lái)啟動(dòng) (具體見 demo4 講解).
"""
def demo4():
"""
這是最終我們想要的實(shí)現(xiàn).
"""
import asyncio # 引入 asyncio 庫(kù)
async def washing1():
await asyncio.sleep(3) # 使用 asyncio.sleep(), 它返回的是一個(gè)可等待的對(duì)象
print('washer1 finished')
async def washing2():
await asyncio.sleep(2)
print('washer2 finished')
async def washing3():
await asyncio.sleep(5)
print('washer3 finished')
"""
事件循環(huán)機(jī)制分為以下幾步驟:
1. 創(chuàng)建一個(gè)事件循環(huán)
2. 將異步函數(shù)加入事件隊(duì)列
3. 執(zhí)行事件隊(duì)列, 直到最晚的一個(gè)事件被處理完畢后結(jié)束
4. 最后建議用 close() 方法關(guān)閉事件循環(huán), 以徹底清理 loop 對(duì)象防止誤用
"""
# 1. 創(chuàng)建一個(gè)事件循環(huán)
loop = asyncio.get_event_loop()
# 2. 將異步函數(shù)加入事件隊(duì)列
tasks = [
washing1(),
washing2(),
washing3(),
]
# 3. 執(zhí)行事件隊(duì)列, 直到最晚的一個(gè)事件被處理完畢后結(jié)束
loop.run_until_complete(asyncio.wait(tasks))
"""
PS: 如果不滿意想要 "多洗幾遍", 可以多寫幾句:
loop.run_until_complete(asyncio.wait(tasks))
loop.run_until_complete(asyncio.wait(tasks))
loop.run_until_complete(asyncio.wait(tasks))
...
"""
# 4. 如果不再使用 loop, 建議養(yǎng)成良好關(guān)閉的習(xí)慣
# (有點(diǎn)類似于文件讀寫結(jié)束時(shí)的 close() 操作)
loop.close()
"""
最終的打印效果:
washer2 finished
washer1 finished
washer3 finished
elapsed time = 5.126561641693115
(畢竟切換線程也要有點(diǎn)耗時(shí)的)
說(shuō)句題外話, 我看有的博主的加入事件隊(duì)列是這樣寫的:
tasks = [
loop.create_task(washing1()),
loop.create_task(washing2()),
loop.create_task(washing3()),
]
運(yùn)行的效果是一樣的, 暫不清楚為什么他們這樣做.
"""
if __name__ == '__main__':
# 為驗(yàn)證是否真的縮短了時(shí)間, 我們計(jì)個(gè)時(shí)
start = time()
# demo1() # 需花費(fèi)10秒
# demo2() # 會(huì)報(bào)錯(cuò): RuntimeWarning: coroutine ... was never awaited
# demo3() # 會(huì)報(bào)錯(cuò): RuntimeWarning: coroutine ... was never awaited
demo4() # 需花費(fèi)5秒多一點(diǎn)點(diǎn)
end = time()
print('elapsed time = ' + str(end - start))
來(lái)源:
https://blog.csdn.net/Likianta/article/details/90123678