4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

await 挙動の違い: JavaScript v.s. Python

Last updated at Posted at 2018-04-10

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 であるよっと。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?