-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Question: Multithreaded C++ library callback hangs #274
Comments
Yes, would be the GIL. If you start the whole machinery from With the GIL released, C++ threads can then call the Python callbacks (the generated wrapper that marshalls the function arguments and the return value will automatically re-acquire the GIL). There being only one GIL, all Python code will still serialize; that is, you can only run one Python callback at any time, starting any others will simply block them for the duration of the current one. There are couple of examples in the test suite: https://github.com/wlav/cppyy/blob/master/test/test_concurrent.py Aside, to run Python on different C++ threads in parallel, sub-interpreters are needed, but those are not supported yet. Is a work-in-progress. |
That is great information I was missing. Thank you! I added the if ( PyGILState_Check() ) { printf("GIL is held by the current thread.\n"); }
else { printf("GIL is not held by the current thread.\n"); } And I see: Does this mean that in my use case, I still need to wait for sub-interpreters implementation? I do not mind if things are done serially at the moment, for I am more interested in functionality and not full performance yet. |
Inside this C++ callback method, I added this at the beginning of the callback. void callback()
{
if ( PyGILState_Check() ) { printf("A - GIL is held\n"); }
else { printf("A - GIL is NOT held\n"); }
Py_BEGIN_ALLOW_THREADS
if ( PyGILState_Check() ) { printf("B - GIL is held\n"); }
else { printf("B - GIL is NOT held\n"); }
// Your C code that doesn't need the GIL
std::cout << " Stored functions count: " << this->processes.size() << std::endl;
Py_END_ALLOW_THREADS
} And I see:
It seems GIL it is still held at the start, but inside |
The word "callback" is doing too much work (you have them internally to your C++ threaded calls, but now it seems to be a top-level one, or maybe not). Let's talk in code, makes it easier. I believe that the following is what we're talking about:
The above shows how the function that is going to run the callbacks ( Let's continue editing this example code until convergence if there are further questions. Now, if you want to run those callbacks into Python into true parallel mode, then yes, they need to run, from C++ I'd guess or maybe I can figure out some higher-level support, in a Python sub-interpreter. That's a WIP (it's a big chunk of work, but I need it myself as it's critical for a project I'm currently working on). |
Yes, I have similar code as you specified. The registration done from Python as you have, but the actual call is done from the C++ code and not Python. So instead of
I thought with |
Not only should ... I have in fact, running the code above:
But you say "C++ multithreaded code calls this run_callback", so you're probably doing something different, which goes back to the point of talking in code as opposed to in prose, but are you calling that function directly from C++ or are you calling that function through |
I will try to get create a small example, it is difficult to show full code for this library. Python creates the functions to be called by C++ library. It will register them in the map. The C++ library calls the run_callback directly, looks up the Python function to call, and calls it. |
Right, so if the function does not originate from Python, it's C++ that needs to release the GIL. When calling C++ methods from C++ methods, Python-side features such as |
Should releasing the GIL happen before the C++ library/thread calls the callback function? My experience is that I cannot release GIL inside the callback function and that seems to be the cause of the hang/deadlock. |
Even before that. The GIL is held by the main thread (I'm still not sure based on your description whether that's a Python or C++ one). It needs to be released before another thread calls the callback. That thread calling the callback shouldn't itself release the GIL until it held it. |
I think that is where my problem lies. I have no control over the C++ library I am binding to and I do not want to go in and modify it if possible. Not sure what options I have here. |
Well, I still don't understand what you are doing. Maybe one final attempt: first, is this a C++ executable with an embedded Python interpreter, or is the application run from the Python interpreter? |
This is an external C++ library that is started from Python. The C++ library is multithreaded and defines different classes. I intend to expose the classes and most of the functionality to Python. Basically Python runs/initiates the C++ code. Would the sub-interpreters or the no GIL version of Python 3.13 help? |
So how is it started from Python, through cppyy? If yes, then whatever is the top-level function (clearly not the equivalent of As for sub-interpreters, that's about running multiple Python codes in parallel from e.g. C++. They still have a GIL, just one each. You are still going to need to release the main thread one first. |
Great suggestion. I added release_gil to the top-level C++ function that is the entry point for the C++ library. And in the callback function I now see: |
Okay, next I need to understand how these callbacks are created. Are these Python functions or JITed C++ functions? And then, how are they passed from Python into C++? If there is no Python involved in the callbacks, they do not need to hold the GIL. If there is, then they do. But e.g. JITed C++ functions that are passed through Python back into C++ do not require the GIL. To illustrate, below is an extension on the example above, executing Python function and C++ ones, respectively, from threads in C++: the former hold the GIL, the latter don't (this is all automatic). Try the use of C++ JITing to generate callbacks for use by your library. If those still hang, then the issue is not the GIL. If they don't hang, then it most likely still is somehow and to say something sensible in that scenario, I'd need to know how they are passed from Python into C++ and how they are stored in C++?
|
I pushed my code to github. You can clone, and do $ git clone https://github.com/amal-khailtash/pysystemc
$ make setup-systemc
$ make uv
$ make venv
$ source .venv/bin/activate
$ make uv-sync This will clone, get SystemC submodule, checkout v2.3.4, and build SystemC library. This is a H/W language similiar to Verilog/VHDL and it is a multithreaded simulation kernel. Normally the kernel expects an external The Once sc_main starts, and instantiates modules, where modules define the design using ports, and signals and processes (methods, (c)threads in SystemC land). There are a few standard fixed callbacks
This is where I put the function callback name/function pointer into a std::map. I could also pass Once simulation starts with There are some examples of pure SystemC designs that you can run using the provided Python script that just
You can use Your help and insight is much appreciated and I find |
I can look in more detail later, but just from looking over At least, I can't find anywhere in your or the systemc code where Python is being initialized. Recommend calling |
I didn't believe I need to call py_initialize! I am not embedding python in C++, but the other way around. Unless I am missing something. Python will start a python application where it accesses the exposed C++ library. |
Ah, so the main executable is the python interpreter itself? That starts systemc, which runs sc_main? Yes, that should definitely be fine that way. |
Yes, here is how things start:
I hope that is clear. I need to draw some diagram for all these process. |
I am working with a multithreaded C++ library that I am trying to bind to. I have a written a new C++ class that extends one of the original C++ library class. A Python class extends this class and defines a few callbacks that should be called by the C++ library.
The callbacks registration is initiated from Python that passes a Python method name to be called. This method is passed to the extended C++ class where it keeps a std::map of Python method name and function pointer. It also registers a dispatcher method in the extended C++ class that will eventually be calling the Python method by looking its name in the map and calling the Python method.
In this dispatcher function, as soon as I try to access the std::map or any variables outside of this function, the process hangs. I am familiar with GIL a bit, but I think this is somehow related to the concurrent multi-threaded aspect of this library. I tried GIL acquire/release, mutex, and also interpreter swap (sub-interpreters). But I do not quite understand the problem and what causes is or how to debug it.
The text was updated successfully, but these errors were encountered: