[C++-sig] Threads and Boost.Python

Patrick Hartling patrick at vrac.iastate.edu
Fri Apr 18 19:08:26 CEST 2003


I was able to sort out the multi-threading issues I was having earlier 
this week with Boost.Python thanks to David Abrahams' suggestions.  What 
I came up with is a relatively simple guard object that is instantiated 
whenever C++ code calls into the Python interpreter.  To make this guard 
work, I had to deal with two key issues:

    1. Ensuring that threads created by C/C++ bootstrapped themselves with
       the Python interpreter.
    2. Preventing deadlocks if the same thread tried to acquire the GIL
       more than once.

Issue #1 was straightforward enough to handle.  The C++ code base that I 
am exposing to Python includes a cross-platform data structure for 
thread-specific data (TSD).  Within the guard, there is a static data 
member that is an instance of this TSD structure.  Our TSD handler is a 
templated type, and the struct I gave it contained two fields:

struct State
{
    State() : isLocked(false), pyState(NULL)
    {}

    bool isLocked;
    PyThreadState* pyState;
};

The boolean field is used to deal with Issue #2 from above; the thread 
state object needs to be different for each unique thread calling into 
the Python interpreter.  The nice thing about our TSD handler is that a 
new instance of the above structure will be instantiated automatically 
for each thread, so whenever a new thread creates an instance of the 
guard, the guard constructor handles filling in the State instance 
fields.  The code from my guard constructor is this:

Guard::Guard() : mMyLock(false)
{
    if ( NULL == mState->pyState )
    {
       mState->pyState = PyThreadState_New(PyInterpreterState_New());
    }

    if ( ! mState->isLocked )
    {
       // Acquire the GIL.
       PyEval_AcquireThread(mState->pyState);
       mState->isLocked = true;
       mMyLock = true;
    }
}

Here, Guard::mState is the static data member that is an instance of our 
TSD handler.

The destructor is this:

Guard::~Guard()
{
    if ( mMyLock && mState->isLocked )
    {
       // Release the GIL.
       PyEval_ReleaseThread(mState->pyState);
       mState->isLocked = false;
    }
}

The last detail is the data member Guard::mMyLock.  That is a boolean to 
handle keep track of which guard instance actually acquired the GIL so 
that it is the one to release it upon destruction.

Dealing with the recursive lock problem was probably the trickiest part. 
  It's too bad that the GIL cannot be locked twice by the same thread, 
but perhaps that behavior doesn't exist for all the threading 
implementations upon which the Python interpreter runs.

On the whole, this guard concept is working very well for me, though I 
haven't even been using it for 48 hours yet.  Conceptually, I think 
something like a "synchronized call" (to steal a Java concept) in 
Boost.Python would help automate this process.  At this time, however, I 
don't know how well my guard will apply to arbitrary calls into the 
Python interpreter from a thread.  It works like a charm for 
boost::python::call_method<>(), and that's what I care about the most 
right now.

Two side issues I ran into involved deciding when to call 
PyEval_InitThreads() and where to put the two macros for allowing and 
disallowing threads.  With the way our software works, the main thread 
basically doesn't do anything once it starts a microkernel's control 
thread.  The primordial thread just sits and blocks on a semaphore 
waiting for the microkernel to shut down.  From there on, all calls into 
the Python interpreter from C++ are guaranteed to come from a thread 
other than the one that started the interpreter.  So, I put 
Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS in a wrapper function 
that is exposed to Python where the Python "main" function makes the 
sempahore wait call.  There will be other cases I'll have to hunt down as 
we expand our use of Boost.Python, but at least I have the general idea 
of what to watch out for.

  -Patrick


-- 
Patrick L. Hartling                     | Research Assistant, VRAC
patrick at vrac.iastate.edu                | 2624 Howe Hall: 1.515.294.4916
http://www.137.org/patrick/             | http://www.vrac.iastate.edu/





More information about the Cplusplus-sig mailing list