3

Assume I am exporting a c++ worker class to python via Boost.Python. The worker will process a task in a different thread. Once completed, the worker will notify the python caller via a callback.

Here's a piece of example c++ code:

class Worker 
{
public:
    void run()
    {
        _thread = std::thread( [=] () {
                //Initialize and acquire the global interpreter lock
                PyEval_InitThreads();

                //Ensure that the current thread is ready to call the Python C API 
                PyGILState_STATE state = PyGILState_Ensure();

                //invoke the python function
                boost::python::call<void>(this->_callback);

                //release the global interpreter lock so other threads can resume execution
                PyGILState_Release(state);   
        });
    }

    void setPyCallback(PyObject * callable)  { _callback = callable; }

private:
    std::thread _thread;
    PyObject * _callback;
};

Now I have the python code in a script file test.py as:

$ cat test.py
import time
import worker

def mycallback():
    print "callback called"

a = worker.Worker()
a.setPyCallback(mycallback)
a.run()

time.sleep(1)

If I run above scripts in an interactive mode, e.g. ipython, it works without a problem.

Problem: However, running these scripts from the command line like python test.py will just stuck at PyGILState_STATE state = PyGILState_Ensure();.

If I understood correctly, the worker was trying acquire the ready state to execute the callback. While unfortunately the main python thread is busy sleeping - deadlock.

Question: What should I change in the python script / c++ code such that executing the python script file can request a task to be done in c++, wait a bit and get the result printed asynchronously?

===================

With @Giulio's hint, I am now able to solve the problem: PyEval_InitThreads should only be called in the main thread, rather than c++-managed thread, as docs said.

By moving the PyEval_InitThreads(); from the lambda to the head of function Worker::run(), the callback part worked flawless now (with python main thread sleeping). However I need to emphasise that PyEval_InitThreads() is still required in run().

xingzhi.sg
  • 423
  • 4
  • 10
  • 1
    I can't answer your question. But I want to suggest you to make sure you're using the GIL in a proper way. From the PyDocs, it looks like you should be able to only call the `PyGILState` functions, and not the `PyEval_InitThreads`. Because it says "After this call (`PyGILState_Release`), Python’s state will be the same as it was prior to the corresponding PyGILState_Ensure() call", which means you'd still be holding the GIL from InitThreads. – Giulio Franco Feb 17 '15 at 09:33
  • You are right! I managed to solve the problem now. Unfortunately I can't accept your answer in a comment :) – xingzhi.sg Feb 17 '15 at 10:08
  • No prob. It was not an answer, it was just an "I think". Maybe you should add your own answer with all the details. It would help you in understanding better what these functions do, and might help someone else in the future. – Giulio Franco Feb 17 '15 at 10:12
  • Yes, plz check the edited question with answers :) – xingzhi.sg Feb 17 '15 at 10:29
  • You should put your answer in the answer and then accept it, if you can. That will take this off the 'unanswered' list. – D. A. Apr 06 '17 at 21:34
  • Calling `PyEval_InitThreads()` from a thread seems to me problematic. Yes, after this call you have the GIL, and can call the `PyGILState_` functions. But note that `PyEval_InitThreads()` is a no-op when called subsequent times. Thus, if the GIL ever gets taken away from elsewhere, your `PyGILState_Ensure()` call will get stuck in an infinite loop. Better to call `PyEval_InitThreads()` and then explicitly call `PyEval_[Save/Restore]Thread()` from the main thread. See e.g. any of [these](https://stackoverflow.com/a/48103921/1279291) [answers](https://stackoverflow.com/a/42667657/1279291). – andreasdr Jan 05 '18 at 08:30

0 Answers0