You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When calling Python functions, the caller must hold the GIL. Otherwise, you'll
likely experience crashes with AccessViolationException or data races, that corrupt memory.
When executing .NET code, consider releasing the GIL to let Python run other
threads. Otherwise, you might experience deadlocks or starvation.
What to do when calling C# from Python
If you are calling C# from Python, and the C# code performs a long-running
operation (e.g. computation, I/O, sleep, acquiring a lock, ...), release the
GIL via PythonEngine.BeginAllowThreads(). Remember to restore it before
returning control to Python:
If your application initializes Python from one thread but is unable to shut it
down from that thread, special care must be taken. Like above, you may allow your
process to shut down on its own, and Python will handle shutting itself down.
However, if you need to shut Python down manually, the call to PythonEngine.EndAllowThreads()
must be omitted. If it is called, the Python runtime won't be able to acquire the GIL
when the call to PythonEngine.Shutdown() is performed, resulting in a deadlock.
Calling into Python
In between, when you call into Python, it is critical to acquire the GIL.
The easiest way to achieve this is with a using(Py.GIL()) block:
dynamicm_result;voidFoo(){// Don't access Python up here.using(Py.GIL()){// Safe to access Python here.dynamicmymodule=PyModule.Import("mymodule");dynamicmyfunction=mymodule.myfunction;m_result=myfunction();}// The following is unsafe: it is accessing a Python attribute// without holding the GIL.Console.Write($"Got the result {m_result.name}");}
Example problem cases
You need to do this when you launch threads in Python and you expect them to
operate in the background; or when you have multiple C# threads that are
calling into Python.
When embedding C# into Python, imagine calling a version of
LongRunningComputation above that does not release the GIL:
We would expect to see "hello" printed immediately and then again every 100ms,
but the loop usually won't run right away.
Finally, say in C# we have a second thread while the main thread isn't
executing anything. When the second thread tries to execute this code:
using(Py.GIL()){dynamicosModule=PyModule.Import("mymodule");stringcwd=osModule.getcwd();System.Console.Write($"Current directory is: {cwd}");}
We'll see the second C# thread block before it prints anything.
When the main C# thread invokes Python code, you'll see both cases unblock as
you'd expect in a multi-threaded Python application. But when the main thread's
control is in C#, Python threads along with C# threads trying to take the GIL
will all be blocked.
How this works
The Python interpreter only actually runs single-threaded. You can create
additional threads, but only one thread at a time has the Global Interpreter
Lock
(GIL),
and only that thread can run. Python threads will automatically release the GIL
when they call a blocking system call, or periodically when the interpreter is
running Python code.
After the Python interpreter initializes, the main thread holds the GIL.
If a thread is executing C# code while holding the GIL, there's nothing that
releases the GIL. Other threads will then be blocked indefinitely from running
Python code. This is the case when you embed Python in a C# application without
explicitly releasing the GIL.
When you embed Python in a C# application, if the main thread holds the GIL but
occasionally calls into Python, you will see degraded performance. Other Python
threads will get a chance to run occasionally, when the main thread happens to
be running Python code, and at the point the Python interpreter decides to cede
control to other Python threads.
The solution above solves the problem by explicitly releasing the GIL when in
C#, and only taking it when needed.