读/写共享资源时协程是否需要锁?

psi*_*dex 2 python asynchronous python-3.x async-await python-asyncio

例如

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())
Run Code Online (Sandbox Code Playgroud)

如果coro1coro2两个接入一个字典/变量,读取和写入,这需要某种形式的互斥/锁?或者它会很好,因为 asyncio 的东西只发生在 1 个线程上?

use*_*ica 5

是的,你仍然需要锁。并发修改不会因为它是通过协程而不是线程发生而变得安全。

asyncio 有自己的专用asyncio.Lock,以及其他同步原语的自己版本,因为关心线程的锁不会相互保护协程,并且等待锁需要通过事件循环发生,而不是通过阻塞线。

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)
    ...
Run Code Online (Sandbox Code Playgroud)

也就是说,由于协程基于协作多任务处理而不是抢占式,有时您可以保证在线程需要锁的情况下不需要锁。例如,如果在临界区期间没有任何协程可以让出控制的点,那么您就不需要锁。

例如,这需要一个锁:

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])
Run Code Online (Sandbox Code Playgroud)

这在技术上不会:

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])
Run Code Online (Sandbox Code Playgroud)

但不锁定会随着代码更改而引入错误,特别是因为以下两个协程中确实需要锁定:

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])
Run Code Online (Sandbox Code Playgroud)

如果两个协程都没有锁,coro2可能会coro1coro1需要独占访问共享资源时中断。