web-dev-qa-db-ja.com

Python asyncio.Lock()は何のためにあるのですか?

コルーチンが将来プリエンプションされる可能性があるためですか?または、クリティカルセクション(IMOは推奨されるべきではありません)からの利回りを使用できるようにしますか?

18
user3761759

スレッドコードでロックを使用するのと同じ理由で、クリティカルセクションを保護するために使用します。 asyncioは主にシングルスレッドコードでの使用を目的としていますが、同時実行が発生しているため(yield fromまたはawaitを押すたびに)、同期が必要になる場合があります。

たとえば、Webサーバーからデータをフェッチし、結果をキャッシュする関数について考えてみます。

async def get_stuff(url):
    if url in cache:
        return cache[url]
    stuff = await aiohttp.request('GET', url)
    cache[url] = stuff
    return stuff

ここで、get_stuffの戻り値を使用する必要がある可能性のある複数のコルーチンが同時に実行されていると仮定します。

async def parse_stuff():
    stuff = await get_stuff()
    # do some parsing

async def use_stuff():
    stuff = await get_stuff()
    # use stuff to do something interesting

async def do_work():
     out = await aiohttp.request("www.awebsite.com")
     # do some work with out


loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(
    parse_stuff(),
    use_stuff(),
    do_work(),
))

ここで、urlからのデータのフェッチが遅いであると仮定します。 parse_stuffuse_stuffの両方が同時に実行される場合、それぞれがstuffをフェッチするためにネットワークを経由する全コストに見舞われます。メソッドをロックで保護する場合は、次のことを回避します。

stuff_lock = asyncio.Lock()

async def get_stuff(url):
    async with stuff_lock:
        if url in cache:
            return cache[url]
        stuff = await aiohttp.request('GET', url)
        cache[url] = stuff
        return stuff

もう1つの注意点は、1つのコルーチンがget_stuff内にあり、aiohttp呼び出しを行い、別のコルーチンがstuff_lockを待機している間、get_stuffを呼び出す必要がまったくない3番目のコルーチンも実行できます。 Lockでのコルーチンブロッキングの影響を受けます。

明らかに、この例は少し工夫されていますが、うまくいけば、asyncio.Lockが役立つ理由がわかります。これにより、クリティカルセクションへのアクセスを必要としない他のコルーチンの実行をブロックすることなく、クリティカルセクションを保護できます。

39
dano