1. ホーム
  2. python

[解決済み] Pythonのasyncioモジュールを使って並行タスクを正しく作成し、実行するには?

2023-08-18 14:44:04

質問

私は、2つの同時実行される Task オブジェクトを、Python 3 の比較的新しい asyncio モジュールを使っています。

簡単に言うと、asyncioは非同期プロセスや同時並行の Task を扱うように設計されているようです。これは await (非同期関数で適用)の使用を促進します。イベントループをブロックすることなく、結果を待ち、使用するコールバックフリーな方法として。(フューチャーとコールバックはまだ実行可能な代替手段です)。

また、これは asyncio.Task() クラスの特殊なサブクラスである Future のサブクラスで、コルーチンをラップするために設計されています。好ましくは asyncio.ensure_future() メソッドを使って呼び出すのが望ましいです。asyncioタスクの使用目的は、独立して実行されているタスクが同じイベントループ内で他のタスクと「同時に」実行することを可能にすることです。私の理解では Tasks の間にあるコルーチンを自動的に駆動し続けるイベントループに接続されている、ということです。 await ステートメントの間で自動的にコルーチンを駆動し続けます。

のいずれかを使用する必要がなく、同時実行タスクが使用できるというアイデアが気に入っています。 Executor クラスのいずれかを使用する必要なく、同時実行タスクが使用できるというアイデアが好きですが、実装に関する詳細な情報はあまり見当たりません。

現在、私がやっている方法はこれです。

import asyncio

print('running async test')

async def say_boo():
    i = 0
    while True:
        await asyncio.sleep(0)
        print('...boo {0}'.format(i))
        i += 1

async def say_baa():
    i = 0
    while True:
        await asyncio.sleep(0)
        print('...baa {0}'.format(i))
        i += 1

# wrap in Task object
# -> automatically attaches to event loop and executes
boo = asyncio.ensure_future(say_boo())
baa = asyncio.ensure_future(say_baa())

loop = asyncio.get_event_loop()
loop.run_forever()

ループする2つのタスクを同時に実行しようとした場合、タスクが内部で await 式がない場合、タスクは while ループにはまり、他のタスクの実行を事実上ブロックしてしまいます (通常の while ループのように)他のタスクの実行を効果的にブロックします。しかし、タスクが (a) 待機するようになると、問題なく並行して実行されるように見えます。

このように await ステートメントは、イベントループにタスク間の切り替えの足場を提供し、同時実行の効果を与えているようです。

内部での出力例 await :

running async test
...boo 0
...baa 0
...boo 1
...baa 1
...boo 2
...baa 2

出力例 がない場合 内部 await :

...boo 0
...boo 1
...boo 2
...boo 3
...boo 4

質問

この実装は、"適切な "例として asyncio ?

のみで動作するということでよろしいでしょうか? Task をブロックポイントとして提供すること ( await 式) を提供する必要があるのでしょうか?

どのように解決するのですか?

イベントループ内で実行されているコルーチンは、他のコルーチンやタスクの実行をブロックします。

  1. を使用して他のコルーチンを呼び出す。 yield from または await (Python3.5+を使用している場合) を使用します。
  2. を返します。

これは、以下の理由からです。 asyncio がシングルスレッドであるためです。イベントループを実行する唯一の方法は、他のコルーチンがアクティブに実行されないことです。使用方法 yield from / await はコルーチンを一時的に停止させ、イベントループが動作する機会を与えます。

あなたのコード例は素晴らしいものですが、多くの場合、非同期I/Oを行わない長時間実行のコードは、そもそもイベントループの内部で実行させたくないのではないでしょうか。そのような場合、多くの場合、イベントループ内で asyncio.loop.run_in_executor を使用してバックグラウンドのスレッドやプロセスでコードを実行する方が理にかなっています。 ProcessPoolExecutor は、タスクが CPU に依存している場合により良い選択です。 ThreadPoolExecutor でない I/O を行う必要がある場合に使われます。 asyncio -でないI/Oが必要な場合に使用されます。

例えば、2つのループは完全にCPUバウンドで、状態を共有しないので、最高のパフォーマンスは ProcessPoolExecutor を使用して、各ループを CPU 間で並列に実行することで最高のパフォーマンスを得ることができます。

import asyncio
from concurrent.futures import ProcessPoolExecutor

print('running async test')

def say_boo():
    i = 0
    while True:
        print('...boo {0}'.format(i))
        i += 1


def say_baa():
    i = 0
    while True:
        print('...baa {0}'.format(i))
        i += 1

if __name__ == "__main__":
    executor = ProcessPoolExecutor(2)
    loop = asyncio.get_event_loop()
    boo = loop.run_in_executor(executor, say_boo)
    baa = loop.run_in_executor(executor, say_baa)

    loop.run_forever()