Releasing the GIL

Hi all, 2 quick questions, will calling a function that doesn’t have the BLOCKING keyword from one that does have it, still properly release the GIL? Example:

void run_me_too()
{
...
}

BLOCKING void run_me()
{
...
run_me_too();
...
}

Or will one have to add the keyword to both?:

BLOCKING void run_me_too()
{
...
}

BLOCKING void run_me()
{
...
run_me_too();
...
}

Since it’s also related to asynchronous operations, does calling Texture.read() in an asynchronous operation release the GIL?:

texFile="file_directory"
pandafile = Filename.fromOsSpecific(texFile)
textGot=Texture()
textGot.read(pandafile)

Apologies if the questions are rather rudimentary, I’m still doing different tests with c++.

Thanks in advance.

No, the GIL is only released by the Python bindings if they notice that the method you are calling has BLOCKING. If a C++ function that is not marked BLOCKING calls another C++ function that is marked BLOCKING, it won’t release the GIL. Therefore, you need to make sure that all functions that may end up calling a BLOCKING function are also themselves marked BLOCKING (assuming they don’t interact with the Python interpreter in some way that prevents doing so, of course).

Calling texture.read() directly from Python will release the GIL, because it is marked BLOCKING. Many other methods that indirectly call it, such as TexturePool.load_texture, are also marked BLOCKING.

Asynchronous loading operations initiated by Panda are typically done in a Panda-created thread and are not affected by the GIL.

Thanks, that clarifies a lot, but there may have been a misunderstanding with one thing, I was asking for the inverse, i.e. a C++ function marked BLOCKING calls another function that is not marked BLOCKING, will the GIL be released? So from Python, I call in an asyncTask, a C++ function marked BLOCKING, but within that marked C++ function, are calls to other functions that aren’t marked BLOCKING, will the GIL be released then?

The BLOCKING keyword affects only the entry point into the C++ code. The bindings ask the Python runtime to release the GIL, then call the C++ function, then ask Python to acquire the GIL again. The binding generator does not have any knowledge of other C++ methods that are called by the blocking C++ method in the meantime. So the lock is not reobtained until the parent-most C++ method returns and passes control back to Python.

The only exception to this is if somewhere deep down in the C++ call stack, an explicit call to the Python runtime is made to obtain the GIL again. Usually this is done when a callback needs to call back into Python code. An example of this is a CallbackNode in the scene graph, or a PythonTask that is run inside the task manager.

So, to take that latter example, AsyncTaskManager.step() will release the GIL, but one of the tasks added to the task manager might reobtain it temporarily so that it can run Python code.

C++ tasks (ie. inheriting from AsyncTask or using GenericAsyncTask) never acquire the GIL to begin with. If you want to create a task that never acquires the GIL, implement it in C++, and you can expose it and add it to the task manager on either the Python end or the C++ end. This is slightly more efficient than having a Python task that calls a C++ function, since that causes the GIL to be held briefly upon entry and exit of the task.

1 Like

It all makes sense now, thanks a lot.