Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
TheJanzap edited this pageAug 22, 2024 ·5 revisions

Threading Support

In a multi-threaded environment, it becomes important to manage the PythonGlobal Interpreter Lock(GIL).

When calling Python functions, the caller must hold the GIL. Otherwise, you'lllikely experience crashes withAccessViolationException or data races, that corrupt memory.

When executing .NET code, consider releasing the GIL to let Python run otherthreads. 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-runningoperation (e.g. computation, I/O, sleep, acquiring a lock, ...), release theGIL viaPythonEngine.BeginAllowThreads(). Remember to restore it beforereturning control to Python:

voidLongRunningComputation(){varstate=PythonEngine.BeginAllowThreads();Thread.Sleep(1000);PythonEngine.EndAllowThreads(state);}

What to do when calling Python from C#

In a multi-threaded environment, initialize Python from within C# with thefollowing code:

PythonEngine.Initialize();m_threadState=PythonEngine.BeginAllowThreads();

When shutting down, either allow the process to exit on its own, or call:

PythonEngine.EndAllowThreads(m_threadState);PythonEngine.Shutdown();

Shutting down from a different thread

If your application initializes Python from one thread but is unable to shut itdown from that thread, special care must be taken. Like above, you may allow yourprocess to shut down on its own, and Python will handle shutting itself down.

However, if you need to shut Python down manually, the call toPythonEngine.EndAllowThreads()must be omitted. If it is called, the Python runtime won't be able to acquire the GILwhen the call toPythonEngine.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 ausing(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 tooperate in the background; or when you have multiple C# threads that arecalling into Python.

When embedding C# into Python, imagine calling a version ofLongRunningComputation above that does not release the GIL:

importDotNetModuleDotNetModule.LongRunningComputation()

All other threads (e.g. GUI threads) would hang for a full second while the C#code sleeps.

On the flip side, when embedding Python into C#, if we have our application'smain thread call:

PythonEngine.Initialize();

But not BeginAllowThreads.

Then, if we have the following Python code:

importtimedefsay_hello():whileTrue:print ("hello")time.sleep(0.1)deflaunch_hello():importthreadinghello_thread=threading.Thread(name="Hello thread",target=say_hello)hello_thread.daemon=Truehello_thread.start()

Which we call from the main thread in C#:

using(Py.GIL()){dynamicmymodule=PyModule.Import("mymodule");mymodule.launch_hello();}

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'texecuting 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 asyou'd expect in a multi-threaded Python application. But when the main thread'scontrol is in C#, Python threads along with C# threads trying to take the GILwill all be blocked.

How this works

The Python interpreter only actually runs single-threaded. You can createadditional threads, but only one thread at a time has theGlobal InterpreterLock(GIL),and only that thread can run. Python threads will automatically release the GILwhen they call a blocking system call, or periodically when the interpreter isrunning 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 thatreleases the GIL. Other threads will then be blocked indefinitely from runningPython code. This is the case when you embed Python in a C# application withoutexplicitly releasing the GIL.

When you embed Python in a C# application, if the main thread holds the GIL butoccasionally calls into Python, you will see degraded performance. Other Pythonthreads will get a chance to run occasionally, when the main thread happens tobe running Python code, and at the point the Python interpreter decides to cedecontrol to other Python threads.

The solution above solves the problem by explicitly releasing the GIL when inC#, and only taking it when needed.

Clone this wiki locally

[8]ページ先頭

©2009-2025 Movatter.jp