The Concurrency Safety (CS) Toolkit aims at unifying different levels of concurrency (single & multi thread and multiprocessing) by abstracting away the different low level datastructures and synchronization mechanisms individually associated with each one of those levels.
It does so by providing a unified concurrency API centered around the
concurrency.cs.Pr.HasCSLMixin class which
is initialized with and provides accessors to an IntEnum variable of type concurrency.cs.En.CSL
("CSL" for "concurrency safety level").
(Note the distinction between concurrency.cs.Pr.HasCSLMixin - which is a Protocol - and
concurrency.cs.Im.HasCSLMixin - which is an implementation of said Protocol. Protocol
subclasses of HasCSLMixin should inherit from the former while implementations should
inherit from the latter. This pattern can be found throughout this library and aims at
achieving a separation of interface (e.g., to be used for type annotations) and implementation. See also
subsection about the intra module structure.)
The concurrency.cs.En.CSL enum has the values specified below.
Each of them comes with certain guarantees which implementations of HasCSLMixin must ensure:
SINGLE_THREAD: TheHasCSLMixinsubclass instance does not have to ensure any kind of concurrency safety.MULTI_THREAD: TheHasCSLMixinsubclass instance guarantees concurrency safety and data synchronization for concurrent access from different threads from the same (sub)process as the one from which this instance was created.MULTI_PROCESS: TheHasCSLMixinsubclass instance guarantees concurrency safety and data synchronization for concurrent access from different threads, including those from different subprocesses. Most of theHasCSLMixinsubclasses provided by this library usemultiprocessing.managers.SyncManagerinstances to handle data synchronization on a lower level when CSL is set toMULTI_PROCESS. This is why most implementations (like, for example,concurrency.csdata.CSMutableLengthSequencewhich is basically a list-like class which also implements theHasCSLMixincontract) also require an optional manager instance in their constructor which must be non-Noneif CSL isMULTI_PROCESSING.
This simplifies the typically error prone implementation of concurrency safe data structures and
allows to "scale" the level of concurrency safety and synchronization based on the actual
needs by simply providing a different concurrency.cs.En.CSL value to the constructor
of the respective HasCSLMixin subclass.
All a programmer of such a custom HasCSLMixin subclass has to take care of is
that, given the implementation is initialized with a CSL c, all the internal and external fields
that the implementation's methods might access have a CSL of at least c - either
explicitly by checking whether those fields are HasCSLMixin instances with an appropriate CSL,
or implicitly by initializing those
fields to appropriate low level Python fields (e.g., by using a multiprocessing.managers.SyncManager
instance in the case of c == MULTI_PROCESSING or by using data structures from
the Python threading package in case of c == MULTI_THREAD).
Particularly useful classes from the CSToolkit are concurrency.cslocks.Pr.CSLockableMixin and
concurrency.csrwlocks.Pr.CSRWLockableMixin and their respective implementations from
the Im namespace in the same packages. Both implement HasCSLMixin and provide
mechanisms for conveniently locking access to methods of their custom subclasses via decorators.
Those decorators are De.cslock forCSLockableMixin subclasses
and De.csrlock & De.cswlock for CSRWLockableMixin subclasses.
Like the names suggest, the first class provides a decorator for regular, non-reentrant method
locking and the second class provides read and non-reentrant write locking decorators
(based on a reader-writer lock implementation from concurrency.csrwlocks).
All methods decorated by any of these lock decorators must
accept at least the following parameters blocking: bool = True, timeout: Optional[float] = None and
_unsafe: bool = False (note that while for blocking and timeout, the
default values provided here are not mandatory but recommended, the _unsafe
parameter must either have no default parameter at all or be False by default). While the first two parameters should be self explanatory for those familiar
with Python locks, the last one can be used to skip
the lock acquisition and should thus be used with caution (this is the reason for the _-prefix as this
feature should only be used by subclasses or other methods from the same class).
A typical use-case is when one method decorated with a non-reentrant
lock calls a different one which is also decorated with the same non-reentrant lock. Without the means
to skip the lock acquisition in the second call, this would end up in a deadlock.
It is also possible to provide specific lock keys to the constructor of both CSLockableMixin
and CSRWLockableMixin and to decorate different methods with different locks (for example,
by decorating one set of methods with @De.csrlock("custom_lock_name") where "custom_lock_name" is a
key provided via the csrwlock_keys parameter of the constructor and a different
set of methods with @De.csrlock which implicitly uses the default
lock key concurrency.cslocks.Co.f_DEFAULT_CSLOCK_KEY). All locks are created
instance wise - i.e., two different instances of CSLockableMixin do not share their internal locks
(the same holds for two different instances of CSRWLockableMixin).
Note that this is just a preview of the most important concepts. This library also provides other utilities such as:
concurrency.csrun.Pr.CSRunnableMixinwhich is aHasCSLMixinsubclass that can be executed by an instance of aconcurrency.csrun.CSRunnableManagerbased on the former's CSL:SINGLE_THREAD: Run in single thread mode. This basically boils down to a regular method call.MULTI_THREAD: Run in a separate thread.MULTI_PROCESS: Run in a separate thread in a newly spawned subprocess.
- Different data structures and synchronization classes - all of which implement
HasCSLMixin. Examples are locks, reader-writer locks, condition objects, values, sequences (former two also providing ac_applymethod for applying arbitrarily complex operations in an atomic manner) and queues. - Picklable managers from
concurrency.managerswhich allow for the sharing of manager instances across processes in order to be able to create and share a newHasCSLMixininstance after a subprocess has already started (manyHasCSLMixinsubclasses often require a manager instance in their constructor in the case of CSL beingMULTI_PROCESS). - Various generic programming tools for typing, inspection and testing (with a special focus on tools for testing code meant to be executed concurrently).
In order to run a static typecheck, open a terminal, go to the project base folder (assuming you have all dependencies
from setup.py installed) and type:
mypy src/
For most of the code provided here - especially the one in the concurrency package - extensive
tests have been written in pytest. In order to run them all (including a mypy static typecheck),
open a terminal and execute the following command from the project base folder (assuming you have all dependencies
from setup.py installed):
pytest --color=yes --mypy --verbose -x -s src/
Note that many of the concurrency tests are implemented by testing for various timeouts. This results in the testing typically taking 1-2h.
The contents of most modules are grouped into one or multiple of the following namespaces (implemented by nesting the contents into classes which serve as "namespaces"):
- En: Contains
enuminstances. - Pr: Contains
Protocols. - Ab: Contains abstract classes.
- Im: Contains non-abstract classes.
- Ca: Contains callables (typically implemented as static methods of class Ca).
- Er: Contains custom Exceptions.
- Wa: Contains custom Warnings.
- Co: Contains constants.
- De: Contains decorators.
Most names follow the typical Python naming conventions (e.g., class names as upper camel case, variables and methods as lower snake case). In order to make efficient use of IDE code completion functionalities, the following additional conventions are used throughout this project:
- Everything callable (including callables like lambdas assigned to local variables inside methods)
has a
c_*,_c_*or__c_*prefix (for public, protected or private fields, respectively). - Every non-callable field which is not a local variable inside a method and is not
an enum field has a
f_*,_f_*or__f_*prefix (for public, protected or private fields, respectively). - Non-callable constant fields which are not a local variable inside a method have a
Finaltype annotation and an all uppercase camel case name except for thef-prefix rule from the last bullet point which still applies. - Enum fields have an all uppercase camel case name.
Release versions correspond to commits to the master branch with a commit tag <version>-RELEASE. Checkout this version via git checkout <version>-RELEASE.
The current release version is: 0.1.0.
See ./LICENSE.md.