異步是怎么一回事?
在傳統(tǒng)的順序編程中,
所有發(fā)送給解釋器的指令會一條條被執(zhí)行。此類代碼的輸出容易顯現(xiàn)和預(yù)測。
但是…
譬如說你有一個(gè)腳本向3
個(gè)不同服務(wù)器請求數(shù)據(jù)。 有時(shí),誰知什么原因,發(fā)送給其中一個(gè)服務(wù)器的請求可能意外地執(zhí)行了很長時(shí)間。想象一下從第二個(gè)服務(wù)器獲取數(shù)據(jù)用了
10
秒鐘。在你等待的時(shí)候,整個(gè)腳本實(shí)際上什么也沒干。如果你可以寫一個(gè)腳本可以不去等待第二個(gè)請求而是僅僅跳過它,然后開始執(zhí)行第三個(gè)請求,然后回到第二個(gè)請求,執(zhí)行之前離開的位置會怎么樣呢。就是這樣。你通過切換任務(wù)最小化了空轉(zhuǎn)時(shí)間。盡管如此,當(dāng)你需要一個(gè)幾乎沒有
I/O
的簡單腳本時(shí),你不想用異步代碼。
還有一件重要的事情要提,所有代碼在一個(gè)線程中運(yùn)行。所以如果你想讓程序的一部分在后臺執(zhí)行同時(shí)干一些其他事情,那是不可能的。
準(zhǔn)備開始
這是 asyncio
主概念最基本的定義
:
·
協(xié)程
—
消費(fèi)數(shù)據(jù)的生成器,但是不生成數(shù)據(jù)。
Python 2.5
介紹了一種新的語法讓發(fā)送數(shù)據(jù)到生成器成為可能。
·
任務(wù)
—
協(xié)程調(diào)度器。如果你觀察下面的代碼,你會發(fā)現(xiàn)它只是讓
event_loop
盡快調(diào)用它的
_step
,同時(shí)
_step
只是調(diào)用協(xié)程的下一步。
class Task(futures.Future):
def __init__(self, coro, loop=None):
super().__init__(loop=loop)
...
self._loop.call_soon(self._step)
def _step(self):
...
try:
...
result = next(self._coro)
except StopIteration as exc:
self.set_result(exc.value)
except BaseException as exc:
self.set_exception(exc)
raise
else:
...
self._loop.call_soon(self._step)
·
事件循環(huán)
—
把它想成
asyncio
的中心執(zhí)行器。
現(xiàn)在我們看一下所有這些如何融為一體。正如我之前提到的,異步代碼在一個(gè)線程中運(yùn)行。
從上圖可知:
?。保⒀h(huán)是在線程中執(zhí)行
2.從隊(duì)列中取得任務(wù)
?。常總€(gè)任務(wù)在協(xié)程中執(zhí)行下一步動作
?。矗绻谝粋€(gè)協(xié)程中調(diào)用另一個(gè)協(xié)程(await
),會觸發(fā)上下文切換,掛起當(dāng)前協(xié)程,并保存現(xiàn)場環(huán)境(變量,狀態(tài)),然后載入被調(diào)用協(xié)程
?。担绻麉f(xié)程的執(zhí)行到阻塞部分(阻塞I/O
,
Sleep
),當(dāng)前協(xié)程會掛起,并將控制權(quán)返回到線程的消息循環(huán)中,然后消息循環(huán)繼續(xù)從隊(duì)列中執(zhí)行下一個(gè)任務(wù)...以此類推
6.
隊(duì)列中的所有任務(wù)執(zhí)行完畢后,消息循環(huán)返回第一個(gè)任務(wù)
異步和同步的代碼對比
現(xiàn)在我們實(shí)際驗(yàn)證異步模式的切實(shí)有效,我會比較兩段 python
腳本,這兩個(gè)腳本除了
sleep
方法外,其余部分完全相同。在第一個(gè)腳本里,我會用標(biāo)準(zhǔn)的
time.sleep
方法,在第二個(gè)腳本里使用
asyncio.sleep
的異步方法。
這里使用 Sleep
是因?yàn)樗且粋€(gè)用來展示異步方法如何操作
I/O
的最簡單辦法。
使用同步 sleep
方法的代碼:
import asyncio import time from datetime import datetime
async def custom_sleep():
print('SLEEP', datetime.now())
time.sleep(1)
async def factorial(name, number):
f = 1
for i in range(2, number+1):
print('Task {}: Compute factorial({})'.format(name, i))
await custom_sleep()
f *= i
print('Task {}: factorial({}) is {}\n'.format(name, number, f))
start = time.time()
loop = asyncio.get_event_loop()
tasks = [
asyncio.ensure_future(factorial("A", 3)),
asyncio.ensure_future(factorial("B", 4)),
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
end = time.time()
print("Total time: {}".format(end - start))
腳本輸出:
Task A: Compute factorial(2) SLEEP 2017-04-06 13:39:56.207479 Task A: Compute factorial(3) SLEEP 2017-04-06 13:39:57.210128 Task A: factorial(3) is 6
Task B: Compute factorial(2) SLEEP 2017-04-06 13:39:58.210778 Task B: Compute factorial(3) SLEEP 2017-04-06 13:39:59.212510 Task B: Compute factorial(4) SLEEP 2017-04-06 13:40:00.217308 Task B: factorial(4) is 24
Total time: 5.016386032104492
使用異步 Sleep
的代碼:
import asyncio import time from datetime import datetime
async def custom_sleep():
print('SLEEP {}\n'.format(datetime.now()))
await asyncio.sleep(1)
async def factorial(name, number):
f = 1
for i in range(2, number+1):
print('Task {}: Compute factorial({})'.format(name, i))
await custom_sleep()
f *= i
print('Task {}: factorial({}) is {}\n'.format(name, number, f))
start = time.time()
loop = asyncio.get_event_loop()
tasks = [
asyncio.ensure_future(factorial("A", 3)),
asyncio.ensure_future(factorial("B", 4)),
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
end = time.time()
print("Total time: {}".format(end - start))
腳本輸出:
Task A: Compute factorial(2) SLEEP 2017-04-06 13:44:40.648665
Task B: Compute factorial(2) SLEEP 2017-04-06 13:44:40.648859
Task A: Compute factorial(3) SLEEP 2017-04-06 13:44:41.649564
Task B: Compute factorial(3) SLEEP 2017-04-06 13:44:41.649943
Task A: factorial(3) is 6
Task B: Compute factorial(4) SLEEP 2017-04-06 13:44:42.651755
Task B: factorial(4) is 24
Total time: 3.008226156234741
從輸出可以看到,異步模式的代碼執(zhí)行速度快了大概兩秒。當(dāng)使用異步模式的時(shí)候(每次調(diào)用 await asyncio.sleep(1)
),進(jìn)程控制權(quán)會返回到主程序的消息循環(huán)里,并開始運(yùn)行隊(duì)列的其他任務(wù)(任務(wù)A或者任務(wù)B)。
當(dāng)使用標(biāo)準(zhǔn)的 sleep
方法時(shí),當(dāng)前線程會掛起等待。什么也不會做。實(shí)際上,標(biāo)準(zhǔn)的
sleep
過程中,當(dāng)前線程也會返回一個(gè)
python
的解釋器,可以操作現(xiàn)有的其他線程,但這是另一個(gè)話題了。
推薦使用異步模式編程的幾個(gè)理由
很多公司的產(chǎn)品都廣泛的使用了異步模式,如 Facebook
旗下著名的
React Native
和
RocksDB
。像
Twitter
每天可以承載
50
億的用戶訪問,靠的也是異步模式編程。所以說
,
通過代碼重構(gòu),或者改變模式方法,就能讓系統(tǒng)工作的更快,為什么不去試一下呢?
來源:
開源中國