For example
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())
If coro1
and coro2
both access a single dictionary/variable, both reading and writing, would it require some sort of mutex/lock? Or would it be fine since asyncio stuff only ever happens on 1 thread?
Yes, you still need locks. Concurrent modification doesn't become safe just because it's happening through coroutines instead of threads.
asyncio has its own dedicated asyncio.Lock
, as well as its own versions of other synchronization primitives, because a lock that cares about threads won't protect coroutines from each other, and waiting for a lock needs to happen through the event loop, not by blocking the thread.
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)
...
That said, since coroutines are based on cooperative multitasking instead of preemptive, you can sometimes guarantee that locks are unnecessary in cases where they would be necessary with threads. For example, if there are no points at which any coroutine could yield control during a critical section, then you don't need a lock.
For example, this needs a lock:
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])
This technically doesn't:
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])
but not locking risks introducing bugs as the code changes, particularly since the following does require locks, in both coroutines:
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])
Without locks in both coroutines, coro2
could interrupt coro1
while coro1
needs exclusive access to the shared resource.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With