mpi4py.util.sync
----------------

.. module:: mpi4py.util.sync
   :synopsis: Synchronization utilities.

.. versionadded:: 4.0.0

The :mod:`mpi4py.util.sync` module provides parallel synchronization
utilities.

Sequential execution
++++++++++++++++++++

.. autoclass:: mpi4py.util.sync.Sequential

   Context manager for sequential execution within a group of MPI processes.

   The implementation is based in MPI-1 point-to-point communication. A process
   with rank *i* waits in a blocking receive until the previous process rank
   *i-1* finish executing and signals the next rank *i* with a send.

   .. automethod:: __init__
   .. automethod:: __enter__
   .. automethod:: __exit__
   .. automethod:: begin
   .. automethod:: end

Global counter
++++++++++++++

.. autoclass:: mpi4py.util.sync.Counter

   Produce consecutive values within a group of MPI processes. The counter
   interface is close to that of `itertools.count`.

   The implementation is based in MPI-3 one-sided operations. A root process
   (typically rank ``0``) holds the counter, and its value is queried and
   incremented with an atomic RMA *fetch-and-add* operation.

   .. automethod:: __init__
   .. automethod:: __iter__
   .. automethod:: __next__
   .. automethod:: next
   .. automethod:: free

Mutual exclusion
++++++++++++++++

.. autoclass:: mpi4py.util.sync.Mutex

   Establish a critical section or mutual exclusion among MPI processes.

   The mutex interface is close to that of `threading.Lock` and
   `threading.RLock`, allowing the use of either recursive or non-recursive
   mutual exclusion. However, a mutex should be used within a group of MPI
   processes, not threads.

   In non-recursive mode, the semantics of `Mutex` are somewhat different than
   these of `threading.Lock`:

   * Once acquired, a mutex is held and owned by a process until released.
   * Trying to acquire a mutex already held raises `RuntimeError`.
   * Trying to release a mutex not yet held raises `RuntimeError`.

   This mutex implementation uses the scalable and fair spinlock algorithm from
   [mcs-paper]_ and took inspiration from the MPI-3 RMA implementation of
   [uam-book]_.

   .. automethod:: __init__
   .. automethod:: __enter__
   .. automethod:: __exit__
   .. automethod:: acquire
   .. automethod:: release
   .. automethod:: locked
   .. automethod:: count
   .. automethod:: free

.. [mcs-paper] John M. Mellor-Crummey and Michael L. Scott.
   Algorithms for scalable synchronization on shared-memory multiprocessors.
   ACM Transactions on Computer Systems, 9(1):21-65, February 1991.
   https://doi.org/10.1145/103727.103729

.. [uam-book] William Gropp, Torsten Hoefler, Rajeev Thakur, Ewing Lusk.
   Using Advanced MPI - Modern Features of the Message-Passing Interface.
   Chapter 4, Section 4.7, Pages 130-131. The MIT Press, November 2014.
   https://mitpress.mit.edu/9780262527637/using-advanced-mpi/

Condition variable
++++++++++++++++++

.. autoclass:: mpi4py.util.sync.Condition

   A condition variable allows one or more MPI processes to wait until they are
   notified by another processes.

   The condition variable interface is close to that of `threading.Condition`,
   allowing the use of either recursive or non-recursive mutual exclusion.
   However, the condition variable should be used within a group of MPI
   processes, not threads.

   This condition variable implementation uses a MPI-3 RMA-based scalable and
   fair circular queue algorithm to track the set of waiting processes.

   .. automethod:: __init__
   .. automethod:: __enter__
   .. automethod:: __exit__
   .. automethod:: acquire
   .. automethod:: release
   .. automethod:: locked
   .. automethod:: wait
   .. automethod:: wait_for
   .. automethod:: notify
   .. automethod:: notify_all
   .. automethod:: free

Semaphore object
++++++++++++++++

.. autoclass:: mpi4py.util.sync.Semaphore

   A semaphore object manages an internal counter which is decremented by each
   `acquire()` call and incremented by each `release()` call. The internal
   counter never reaches a value below zero; when `acquire()` finds that it is
   zero, it blocks and waits until some other process calls `release()`.

   The semaphore interface is close to that of `threading.Semaphore` and
   `threading.BoundedSemaphore`, allowing the use of either bounded (default)
   or unbounded semaphores. With a bounded semaphore, the internal counter
   never exceeds its initial value; otherwise `release()` raises `ValueError`.

   This semaphore implementation uses a global `Counter` and a `Condition`
   variable to handle waiting and and notification.

   .. automethod:: __init__
   .. automethod:: __enter__
   .. automethod:: __exit__
   .. automethod:: acquire
   .. automethod:: release
   .. automethod:: free


Examples
++++++++

.. code-block:: python
   :name: test-sync-1
   :caption: :file:`test-sync-1.py`
   :emphasize-lines:  2,6-9
   :linenos:

   from mpi4py import MPI
   from mpi4py.util.sync import Counter, Sequential

   comm = MPI.COMM_WORLD

   counter = Counter(comm)
   with Sequential(comm):
      value = next(counter)
   counter.free()

   assert comm.rank == value

.. code-block:: python
   :name: test-sync-2
   :caption: :file:`test-sync-2.py`
   :emphasize-lines:  2,6-11
   :linenos:

   from mpi4py import MPI
   from mpi4py.util.sync import Counter, Mutex

   comm = MPI.COMM_WORLD

   mutex = Mutex(comm)
   counter = Counter(comm)
   with mutex:
      value = next(counter)
   counter.free()
   mutex.free()

   assert (
      list(range(comm.size)) ==
      sorted(comm.allgather(value))
   )


.. Local variables:
.. fill-column: 79
.. End:
