Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/x42005e1f/aiologic
GIL-powered* locking library for Python
https://github.com/x42005e1f/aiologic
async-await asyncio concurrency eventlet gevent greenlet library locking python synchronization trio
Last synced: about 1 month ago
JSON representation
GIL-powered* locking library for Python
- Host: GitHub
- URL: https://github.com/x42005e1f/aiologic
- Owner: x42005e1f
- License: isc
- Created: 2024-09-10T22:52:14.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2024-11-02T18:27:38.000Z (about 2 months ago)
- Last Synced: 2024-11-02T19:03:30.366Z (about 2 months ago)
- Topics: async-await, asyncio, concurrency, eventlet, gevent, greenlet, library, locking, python, synchronization, trio
- Language: Python
- Homepage: https://pypi.org/project/aiologic/
- Size: 129 KB
- Stars: 5
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.rst
- License: LICENSE
Awesome Lists containing this project
README
========
aiologic
========**aiologic** is an async-aware library for tasks synchronization and their
communication in different threads and different event loops. Let's take a look
at the example:.. code:: python
from threading import Thread
import anyio
from aiologic import Lock
lock = Lock()
async def func(i, j):
print(f"thread={i} task={j} started")async with lock:
await anyio.sleep(1)print(f"thread={i} task={j} stopped")
async def main(i):
async with anyio.create_task_group() as tasks:
for j in range(2):
tasks.start_soon(func, i, j)for i in range(2):
Thread(target=anyio.run, args=[main, i]).start()It prints something like this:
.. code-block::
thread=0 task=0 started
thread=1 task=0 started
thread=0 task=1 started
thread=1 task=1 started
thread=0 task=0 stopped
thread=1 task=0 stopped
thread=0 task=1 stopped
thread=1 task=1 stoppedAs you can see, when using ``aiologic.Lock``, tasks from different event loops
are all able to acquire a lock. In the same case if you use ``anyio.Lock``, it
will raise a ``RuntimeError``. And ``threading.Lock`` will cause a deadlock.Why?
====Cooperative (coroutines, greenlets) and preemptive (threads) multitasking are
not usually used together. But there are situations when these so different
styles need to coexist:* Interaction of two or more frameworks that cannot be run in the same event
loop (e.g. a GUI framework with any other framework).
* Parallelization of code whose synchronous part cannot be easily delegated to
a thread pool (e.g. a CPU-bound network application that needs low
response times).
* Simultaneous use of incompatible concurrency libraries in different threads
(e.g. due to legacy code).Known solutions (only for some special cases) use one of the following ideas:
- Delegate waiting to a thread pool (executor), e.g. via ``run_in_executor()``.
- Delegate calling to an event loop, e.g. via
``call_soon_threadsafe()``.
- Perform polling via timeouts and non-blocking calls.All these ideas have disadvantages. Polling consumes a lot of CPU resources,
actually blocks the event loop for a short time, and has poor responsiveness.
The ``call_soon_threadsafe()`` approach does not actually do any real work
until the event loop scheduler handles a callback, and in the case of a queue
only works when there is only one consumer. The ``run_in_executor()`` approach
requires a worker thread per call and has issues with cancellation and
timeouts:.. code:: python
import asyncio
import threadingfrom concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor(8)
semaphore = threading.Semaphore(0)async def main():
loop = asyncio.get_running_loop()for _ in range(8):
try:
await asyncio.wait_for(loop.run_in_executor(
executor,
semaphore.acquire,
), 0)
except asyncio.TimeoutError:
passprint('active threads:', threading.active_count()) # 1
asyncio.run(main())
print('active threads:', threading.active_count()) # 9 - wow, thread leak!
# program will hang until you press Control-C
However, *aiologic* has none of these disadvantages. Using its approach based
on low-level events, it gives you much more than you can get with alternatives.
That's why it's there, and that's why you're here.Features
========* Python 3.8+ support
* `CPython `_ and `PyPy `_ support
* Pickling and weakrefing support
* Cancellation and timeouts support
* Optional `Trio-style checkpoints
`_:* enabled by default for Trio itself
* disabled by default for all others* Only one checkpoint per asynchronous call:
* exactly one context switch if checkpoints are enabled
* zero or one context switch if checkpoints are disabled* Fairness wherever possible (with some caveats)
* Thread safety wherever possible
* Zero required dependencies
* Lock-free implementationSynchronization primitives:
* Semaphores: counting and bounded
* Locks: primitive, ownable and reentrant
* Capacity limiters: simple and reentrant
* Condition variables
* Barriers: single-use and cyclic
* Events: one-time, reusable and countdown
* Resource guardsCommunication primitives:
* Queues: FIFO, LIFO and priority
Supported concurrency libraries:
* `asyncio `_
and `trio `_ (coroutine-based)
* `eventlet `_
and `gevent `_ (greenlet-based)All synchronization and communication primitives are implemented entirely on
effectively atomic operations, which gives `an incredible speedup on PyPy
`_ compared
to alternatives from the threading module. All this works because of GIL, but
per-object locks also ensure that `the same operations are still atomic
`_, so aiologic also
works when running in a `free-threaded mode
`_.