Simply speaking, thread-safe
means that it is safe when more than one thread
access the same resource and I know Asyncio
use a single thread
fundamentally.
However, more than one Asyncio Task
could access a resource multiple time at a time like multi-threading
.
For example DB connection(if the object is not thread-safe
and supports Asyncio
operation).
Task A
and Task B
accessing the same DB object. Task A
.Task A
await
IO operation on the DB object.(it will take long time enough)Task B
Step3
's IO operation is still in progress(not done).Task B
await
IO operation on the same DB object.Task B
is trying to access the same object at a time. Is it completely safe in Asyncio
and if so, what does it make safe?
What is asyncio? Asyncio stands for asynchronous input output and refers to a programming paradigm which achieves high concurrency using a single thread or event loop. The model isn't novel to Python and is implemented in other languages and frameworks too, the most prominent being JavaScript's NodeJS.
How many times should Asyncio run () be called? It should be used as a main entry point for asyncio programs, and should ideally only be called once. New in version 3.7.
While the name of asyncio may make us think that this library is only good for I/O operations, it has functionality to handle other types of operations as well by interoperating with multithreading and multiprocessing.
You can create multiple threads and run different event loops in each of them.
Using the same asyncio object from multiple tasks is safe in general. As an example, aiohttp has a session object, and it is expected for multiple tasks to access the same session "in parallel".
if so, what does it make safe?
The basic architecture of asyncio allows for multiple coroutines to await
a single future result - they will simply all subscribe to the future's completion, and all will be scheduled to run once the result is ready. And this applies not only to coroutines, but also to synchronous code that subscribes to the future using add_done_callback
.
That is how asyncio will handle your scenario: tasks A and B will ultimately subscribe to some future awaited by the DB object and. Once the result is available, it will be delivered to both of them, in turn.
Pitfalls typically associated with multi-threaded programming do not apply to asyncio because:
Unlike with threads, it is very predictable where a context switch can occur - just look at await
statements in the code (and also async with
and async for
- but those are still very visible keywords). Anything between them is, for all intents and purposes, atomic. This eliminates the need for synchronization primitives to protect objects, as well as the mistakes that result from mishandling such tools.
All access to data happens from the thread that runs the event loop. This eliminates the possibility of a data race, reading of shared memory that is being concurrently written to.
One scenario in which multi-tasking could fail is multiple consumers attaching to the same stream-like resource. For example, if several tasks try to await reader.read(n)
on the same reader
stream, exactly one of them will get the new data1, and the others will keep waiting until new data arrives. The same applies to any shared streaming resource, including file descriptors or generators shared by multiple objects. And even then, one of the tasks is guaranteed to obtain the data, and the integrity of the stream object will not be compromised in any way.
1 One task receiving the data only applies if the tasks share the reader and each task separately calls data = await reader.read(n)
. If one were to extract a future with fut = asyncio.ensure_future(reader.read(n))
(without using await
), share the future among multiple tasks, and await it in each task with data = await fut
, all tasks would be notified of the particular chunk of data that ends up returned by that future.
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