bpo-43352: Add a Barrier object in asyncio lib (GH-24903)

Co-authored-by: Yury Selivanov <yury@edgedb.com>
Co-authored-by: Andrew Svetlov <andrew.svetlov@gmail.com>
This commit is contained in:
Duprat 2022-03-25 23:01:21 +01:00 committed by GitHub
parent 20e6e5636a
commit d03acd7270
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 856 additions and 5 deletions

View file

@ -28,6 +28,7 @@ asyncio has the following basic synchronization primitives:
* :class:`Condition`
* :class:`Semaphore`
* :class:`BoundedSemaphore`
* :class:`Barrier`
---------
@ -340,6 +341,115 @@ BoundedSemaphore
.. versionchanged:: 3.10
Removed the *loop* parameter.
Barrier
=======
.. class:: Barrier(parties, action=None)
A barrier object. Not thread-safe.
A barrier is a simple synchronization primitive that allows to block until
*parties* number of tasks are waiting on it.
Tasks can wait on the :meth:`~Barrier.wait` method and would be blocked until
the specified number of tasks end up waiting on :meth:`~Barrier.wait`.
At that point all of the waiting tasks would unblock simultaneously.
:keyword:`async with` can be used as an alternative to awaiting on
:meth:`~Barrier.wait`.
The barrier can be reused any number of times.
.. _asyncio_example_barrier:
Example::
async def example_barrier():
# barrier with 3 parties
b = asyncio.Barrier(3)
# create 2 new waiting tasks
asyncio.create_task(b.wait())
asyncio.create_task(b.wait())
await asyncio.sleep(0)
print(b)
# The third .wait() call passes the barrier
await b.wait()
print(b)
print("barrier passed")
await asyncio.sleep(0)
print(b)
asyncio.run(example_barrier())
Result of this example is::
<asyncio.locks.Barrier object at 0x... [filling, waiters:2/3]>
<asyncio.locks.Barrier object at 0x... [draining, waiters:0/3]>
barrier passed
<asyncio.locks.Barrier object at 0x... [filling, waiters:0/3]>
.. versionadded:: 3.11
.. coroutinemethod:: wait()
Pass the barrier. When all the tasks party to the barrier have called
this function, they are all unblocked simultaneously.
When a waiting or blocked task in the barrier is cancelled,
this task exits the barrier which stays in the same state.
If the state of the barrier is "filling", the number of waiting task
decreases by 1.
The return value is an integer in the range of 0 to ``parties-1``, different
for each task. This can be used to select a task to do some special
housekeeping, e.g.::
...
async with barrier as position:
if position == 0:
# Only one task print this
print('End of *draining phasis*')
This method may raise a :class:`BrokenBarrierError` exception if the
barrier is broken or reset while a task is waiting.
It could raise a :exc:`CancelledError` if a task is cancelled.
.. coroutinemethod:: reset()
Return the barrier to the default, empty state. Any tasks waiting on it
will receive the :class:`BrokenBarrierError` exception.
If a barrier is broken it may be better to just leave it and create a new one.
.. coroutinemethod:: abort()
Put the barrier into a broken state. This causes any active or future
calls to :meth:`wait` to fail with the :class:`BrokenBarrierError`.
Use this for example if one of the taks needs to abort, to avoid infinite
waiting tasks.
.. attribute:: parties
The number of tasks required to pass the barrier.
.. attribute:: n_waiting
The number of tasks currently waiting in the barrier while filling.
.. attribute:: broken
A boolean that is ``True`` if the barrier is in the broken state.
.. exception:: BrokenBarrierError
This exception, a subclass of :exc:`RuntimeError`, is raised when the
:class:`Barrier` object is reset or broken.
---------