Требуют ли сопрограммы блокировок при чтении / записи общего ресурса? - PullRequest
1 голос
/ 07 мая 2020

Например,

shared = {}

async def coro1():
    # do r/w stuff with shared 

async def coro2():
    # do r/w stuff with shared 

asyncio.create_task(coro1())
asyncio.create_task(coro2())

Если coro1 и coro2 оба обращаются к одному словарю / переменной, как для чтения, так и для записи, потребуется ли какой-то мьютекс / блокировка? Или это было бы нормально, поскольку вещи asyncio всегда происходят только в 1 потоке?

1 Ответ

4 голосов
/ 07 мая 2020

Да, замки еще нужны. Одновременная модификация небезопасна только потому, что она происходит через сопрограммы вместо потоков.

asyncio имеет свой собственный выделенный asyncio.Lock, а также свои собственные версии других примитивов синхронизации, потому что блокировка, которая заботится о потоках, не будет защищать сопрограммы друг от друга, и ожидание блокировки должно происходить через событие l oop, а не за счет блокировки потока.

shared = {}
lock = asyncio.Lock()

async def coro1():
    ...
    async with lock:
        await do_stuff_with(shared)
    ...

async def coro2():
    ...
    async with lock:
        await do_stuff_with(shared)
    ...

Это сказал, поскольку сопрограммы основаны на совместной многозадачности, а не на вытеснении, вы иногда можете гарантировать, что блокировки не нужны в тех случаях, когда они будут необходимы для потоков. Например, если нет точек, в которых любая сопрограмма могла бы передать управление во время критического раздела, тогда вам не нужна блокировка.

Например, для этого нужна блокировка:

async def coro1():
    async with lock:
        for key in shared:
            shared[key] = await do_something_that_could_yield(shared[key])

async def coro2():
    async with lock:
        for key in shared:
            shared[key] = await do_something_that_could_yield(shared[key])

Технически этого не происходит:

async def coro1():
    for key in shared:
        shared[key] = do_something_that_cant_yield(shared[key])

async def coro2():
    for key in shared:
        shared[key] = do_something_that_cant_yield(shared[key])

, но без блокировки рискует ввести ошибки по мере изменения кода, особенно потому, что следующий требует блокировки, в обе сопрограммы:

async def coro1():
    async with lock:
        for key in shared:
            shared[key] = await do_something_that_could_yield(shared[key])

async def coro2():
    async with lock:
        for key in shared:
            shared[key] = do_something_that_cant_yield(shared[key])

Без блокировок в обеих сопрограммах coro2 может прервать coro1, а coro1 требует монопольного доступа к общему ресурсу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...