# [[concurrency|Concurrency]] and [[async|Async Programming]] in [[python|Python]] - [[thread|Threading]], [[process]]es, and coroutines. - [Asyncio | Pysheeet](https://www.pythonsheets.com/notes/python-asyncio.html) ## Threading - With `threading` library, real OS level threads are created, but GIL explicitly prevents multiple threads from executing Python bytecode at the same time. - GIL will suspend the thread every 5ms. - Delegating synchronous tasks to thread with `asyncio.to_thread` ## `asyncio` - `coroutine` - `async def` functions (most likely with `await` inside) are coroutine functions and return coroutine objects. - `coroutine` is an object that encapsulates the ability to resume (by `await`) an underlying function that has been suspended before completion. - Coroutines can either be `await`-ed, or be ran by putting into event loop. `asyncio.run()`, `loop.run_until_complete()`, `asyncio.create_task() -> Task` - Inside a coroutine, `asyncio.sleep()` should be used instead of `time.sleeo()`, so that the single main thread is not blocked. - A well designed coroutine should be nonblocking, i.e. releases GIL when possible. - `Task` vs `Future` - `Task` is a subclass of `Future`, `Future` is closer to `Promise` in JavaScript and is mostly used for framework. - `Future` has methods such as `set_value()`, `done()`, `cancel()` - `asyncio.Future` and `concurrent.futures.future` are mostly the same, but the latter is not `await`-able. - `asyncio.get_event_loop` (globally) and `asyncio.get_running_loop` (inside a coroutine) to get the loop -- manually managing the loop is no longer needed. - To cancel tasks, use `Task.cancel()`, where `CancelledError` will be raised at the `await` expression. In normal exit, `StopIteration` is raised. - `await asyncio.sleep(0)` can be used as a gap to return control so that other threads can proceed. However CPU intensive operations should still be delegated to processes. - For blocking functions: - Run them in a (thread or process) executor with `loop.run_in_executor()` - Or delegate to another thread with `asyncio.to_thread()` - Objects cannot be used as sentinel since they lose identity after serialization and deserialization. `None` is not suitable if it can occur in the data stream. - `futures.as_complete` forms a generator that `yield`s future result as they're done, which can be combined with `tqdm` to create progress bar. - `futures.result()` returns a value or raises exceptions caught. - Other `async` syntactic sugars - Asynchronous context manager and generators, with `__aenter__`, `__aexit__`, and `__anext__`, `__aiter__`. - Async generators. - Async iterators, with `def __aiter__` and `async def __anext__`. ## Libraries - `greenlet` can be used for cooperative multitasking without special syntax, it's used by `SQLAlchemy` internally. `gevent` makes Python's `socket` library nonblocking. - `lelo` library allows parallelizing functions simply by `@parallelize` decorator - Unlike [[js|JavaScript]], Python supports 3rd party async runtime such as `Curio` and `Trio`, which may provide more sensible API. (since `asyncio` allows other implementations of event loop)