web-dev-qa-db-ja.com

並列非同期IO Pythonのコルーチン

簡単な例:無関係な2つのHTTPリクエストを並行して行う必要があります。それを行う最も簡単な方法は何ですか?私はそれがそのようなものであることを期待しています:

async def do_the_job():
    with aiohttp.ClientSession() as session:
        coro_1 = session.get('http://httpbin.org/get')
        coro_2 = session.get('http://httpbin.org/ip')
        return combine_responses(await coro_1, await coro_2)

つまり、IO操作を開始し、それらの結果を待機して、効果的に並列実行できるようにします。これは、asyncio.gatherで実現できます。

async def do_the_job():
    with aiohttp.ClientSession() as session:
        coro_1 = session.get('http://example.com/get')
        coro_2 = session.get('http://example.org/tp')
        return combine_responses(*(await asyncio.gather(coro_1, coro_2)))

次に、いくつかの複雑な依存関係構造が必要です。すべての前提条件が整ったときに操作を開始し、結果が必要なときに結果を取得したいと考えています。イベントループによって個別に管理されるコルーチンとは別のタスクを作成するasyncio.ensure_futureを以下に示します。

async def do_the_job():
    with aiohttp.ClientSession() as session:
        fut_1 = asyncio.ensure_future(session.get('http://httpbin.org/ip'))
        coro_2 = session.get('http://httpbin.org/get')
        coro_3 = session.post('http://httpbin.org/post', data=(await coro_2)
        coro_3_result = await coro_3
        return combine_responses(await fut_1, coro_3_result)

ロジックフローのコルーチンで並列非ブロッキングIOを実現するために、asyncio.ensure_futureまたはasyncio.gather(実際にはasyncio.ensure_future)?より「冗長」な方法はありますか?

通常、開発者が最適なパフォーマンスを得るには、どのコルーチンが個別のタスクになり、前述の関数を使用する必要があるかを考える必要があるのは本当ですか?

イベントループで複数のタスクなしでコルーチンを使用することに意味がありますか?

実際のイベントループタスクはどのくらい「重い」のでしょうか。確かに、それらはOSのスレッドやプロセスよりも「軽量」です。そのようなタスクを最小限に抑えるためにどの程度努力する必要がありますか?

11
George Sovetov

無関係な2つのHTTPリクエストを並行して行う必要があります。それを行う最も簡単な方法は何ですか?

import asyncio
import aiohttp


async def request(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.text()


async def main():
    results = await asyncio.gather(
        request('http://httpbin.org/delay/1'),
        request('http://httpbin.org/delay/1'),
    )
    print(len(results))


loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(main())
    loop.run_until_complete(loop.shutdown_asyncgens())
finally:
    loop.close()

はい、asyncio.gatherを使用して同時実行を実現するか、asyncio.ensure_futureを使用してタスクを作成できます。

次に、いくつかの複雑な依存関係構造が必要ですか?すべての前提条件が整ったときに操作を開始し、結果が必要なときに結果を取得したいと考えています。

あなたが提供したコードはうまくいきますが、異なるコルーチンで並行フローを分割し、再びasyncio.gatherを使用する方が良いでしょう:

import asyncio
import aiohttp


async def request(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.text()


async def get_ip():
    return await request('http://httpbin.org/ip')


async def post_from_get():
    async with aiohttp.ClientSession() as session:
        async with session.get('http://httpbin.org/get') as resp:
            get_res = await resp.text()
        async with session.post('http://httpbin.org/post', data=get_res) as resp:
            return await resp.text()


async def main():
    results = await asyncio.gather(
        get_ip(),
        post_from_get(),
    )
    print(len(results))


loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(main())
    loop.run_until_complete(loop.shutdown_asyncgens())
finally:
    loop.close()

通常、開発者が最適なパフォーマンスを得るには、どのコルーチンが個別のタスクになり、前述の関数を使用する必要があるかを考える必要があるのは本当ですか?

Asyncioを使用しているので、パフォーマンスを向上させるためにいくつかのジョブを同時に実行する必要があるでしょう。 asyncio.gatherは、「結果をより速く取得するために、これらのジョブを同時に実行する」という言い方です。

パフォーマンスを得るためにどのジョブを同時に実行する必要があるかを考える必要がない場合は、単純な同期コードで大丈夫かもしれません。

イベントループで複数のタスクなしでコルーチンを使用することに意味がありますか?

コードでは、必要ない場合は手動でタスクを作成する必要はありません。この回答のスニペットはどちらもasyncio.ensure_futureを使用していません。ただし、内部ではasyncioは常にタスクを使用します(たとえば、asyncio.gatherはタスク自体を使用します)。

実際のイベントループタスクはどのくらい「重い」のでしょうか。確かに、それらはOSのスレッドやプロセスよりも「軽量」です。そのようなタスクを最小限に抑えるためにどの程度努力する必要がありますか?

非同期プログラムの主なボトルネックは(ほとんどの場合)ネットワークです:非同期コルーチン/タスクの数を心配する必要はありません。

9