pythonmultithreadingpython-asynciogil

Is asyncio affected by the GIL?


On this page I read this:

Coroutines in the asyncio module are not limited by the Global Interpreter Lock or GIL.

But how is this possible if both the asyncio event loop and the threading threads are running in a single Python process with GIL?

As far as I understand, the impact of the GIL on asyncio will not be as strong as on threading, because in the case of threading, the interpreter will switch to useless operations, such as time.sleep(). Therefore, when working with asyncio, it is recommended to use asyncio.sleep().

I understand that these tools are designed for slightly different things, threading is more often used to execute "legacy" blocking code for IO-Bound operations, and asyncio for non-blocking code.


Solution

  • There are really two kinds of answers to this, depending on whether you take the GIL as the concrete implementation or the conceptual limitation. The answer is No'ish for the former, and Yes'ish for the latter.


    No, asyncio concurrency is not bound to the GIL.

    The GIL exists to synchronise thread concurrency: When more than one thread is active at once, only the thread that owns the GIL may execute Python code. This means that if another thread needs to run, ownership of the GIL must pass from the current executing thread to the other thread. This is realised via preemptive concurrency, meaning the currently executing thread is forcefully paused regularly.
    As such, the GIL is the mechanism handling the switching of control between the currently executing thread and all other threads. This is a very wasteful mechanism that becomes worse the more threads there are.

    In contrast, asyncio has its own concurrency synchronisation: async/await provides cooperative concurrency by design. That means the currently executing task can voluntarily yield control to any other waiting task. This allows the asyncio event loop to freely schedule tasks.
    As such, the GIL is not involved in switching control between the currently executing task and all other tasks. An async framewok such as asyncio can achieve very efficient task switching, regardless of the number of tasks.


    Yes, asyncio is bound by the GIL.

    While asyncio can efficiently switch tasks, it cannot run more than one task at any moment. As such, it does not actually avoid the conceptual limitation of the GIL: running more than one operation at once. While asyncio applications avoid the inefficiency of the GIL for many operations, it does not avoid the limitation of the GIL for parallel operations.
    Notably, solving this limitation would require solving the exact same challenge as for removing the GIL: providing a safe synchronisation when executing Python code in parallel.

    In addition, asyncio still cannot execute blocking code concurrently as tasks but must fallback to threading for these. Thus, asyncio's backend for executing blocking code concurrently is directly bound by the GIL.