TL;DR
- JavaScript では Promise を new した瞬間に処理がトリガされる
- Python ではawait した瞬間に処理がトリガされる(ように私には見える)
特に、Python 使う場合は注意。思ったとおりの並列処理ができてないかもしれないかもよ。
サンプルコード
- 1つめの Promise/コルーチン を作成し
- CPU主体処理(というかブロッキングな処理)を実施し
- 2つめの Promise/コルーチン を作成し
- 両方の Promise/コルーチン が終わるのを待つ
なんと上記のロジックをそのまま書くと Python と JavaScript で処理時間が異なってしまう。
JavaScript では以下の処理は 6秒で終わる。
await.js
const asleep = async (val) => {
await new Promise(r => setTimeout(() => r(), val*1000));
return val;
}
const doCPUIntensiveProcess = (val) => {
const endTime = Date.now() + val * 1000;
for (;;) {
{hello: 'world'};
if (Date.now() > endTime)
break;
}
return 1
}
const main = async () => {
const start = Date.now()
const p1 = asleep(3); // sleep 3sec
const result = doCPUIntensiveProcess(5); // work 5sec, returns 1
const p2 = asleep(result); // sleep 1sec
console.log(await Promise.all([p1, p2]), result);
console.log(`${((Date.now()-start)/1000).toFixed(0)}secs passed`)
};
main();
Python では以下の処理は 8秒 かかってしまう。
await.py
import asyncio
import time
async def asleep(val):
await asyncio.sleep(val)
return val
def doCPUIntensiveProcess(val):
end_time = time.time() + val
while True:
{'hello': 'world'}
if time.time() > end_time:
break
return 1
async def main():
start = time.time()
t1 = asleep(3)
result = doCPUIntensiveProcess(5)
t2 = asleep(result)
print(await asyncio.gather(t1,t2), result)
print('{:.0f}secs passed'.format(time.time()-start))
asyncio.get_event_loop().run_until_complete(main())
なぜ違いがでるかというと、以下のとおりだからだ。
- JavaScript では Promise を new した瞬間に処理がトリガされる
- Python ではawait した瞬間に処理がトリガされる(ように私には見える)
改版
Python で JavaScript と同様の並列性を持たせるにはどうすればいいのだろうか? await して非同期関数をトリガさせる前に、doCPUIntensiveProcess
が走ってしまうのが原因なので、こいつの評価を遅らせればよさそうだ。ということで、以下のように変更すれば、処理時間が6秒で済む。
await2.py
import asyncio
import time
async def asleep(val):
await asyncio.sleep(val)
return val
def doCPUIntensiveProcess(val):
end_time = time.time() + val
while True:
{'hello': 'world'}
if time.time() > end_time:
break
return 1
async def to_coroutine(f, args):
return f(*args)
async def main(loop):
start = time.time()
t1 = loop.create_task(asleep(3))
result = await loop.create_task(to_coroutine(doCPUIntensiveProcess, [5]))
t2 = asleep(result)
print (await asyncio.gather(t1,t2) , result)
print('{:.0f}secs passed'.format(time.time()-start))
loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))
さいごに
python は Promise じゃなくて コルーチンだから、この挙動の方が正しいんだろうなあ。同じキーワード await を使っているから混乱するだけで。promise に対応するのは task/future であるよっと。