"A Fundamental Turn Toward Concurrency in Software"

Nick Coghlan ncoghlan at iinet.net.au
Sat Jan 8 02:58:07 EST 2005


Steve Horsley wrote:
> But my understanding is that the current Python VM is single-threaded 
> internally,
> so even if the program creates multiple threads, just one core will be 
> dividing
> its time between those "threads".

Not really.

The CPython interpreter does have a thing called the 'Global Interpreter Lock' 
which synchronises access to the internals of the interpreter. If that wasn't 
there, Python threads could corrupt the data structures. In order to do anything 
useful, Python code must hold this lock, which leads to the frequent 
misapprehension that Python is 'single-threaded'.

However, the threads created by the Python threading mechanism are real OS 
threads, and the work load can be distributed between different cores.

In practice, this doesn't happen for a pure Python program, since any running 
Python code must hold the interpreter lock. The Python threads end up getting 
timesliced instead of running in parallel. Genuine concurrency with pure Python 
requires running things in separate processes (to reliably get multiple 
instances of the Python interpreter up and running).

Python threads are mainly intended to help deal with 'slow' I/O operations like 
disk and network access - the C code that implements those operations *releases* 
the GIL before making the slow call, allowing other Python threads to run while 
waiting for the I/O call to complete. This behaviour means threading can give 
*big* performance benefits on even single-CPU machines, and is likely to be the 
biggest source of performance improvements from threading.

However, on multi-processor machines, it is also handy if a CPU-intensive 
operation can be handled on one core, while another core keeps running Python code.

Again, this is handled by the relevant extension releasing the GIL before 
performing its CPU-intensive operations and reacquiring the GIL when it is done.

So Python's concurrency is built in a couple of layers:

Python-level concurrency:
   Multiple processes for true concurrency
   Time-sliced concurrency within a process (based on the GIL)

C-level concurrency:
   True concurrency if GIL is released when not needed

In some cases, problems with multi-threading are caused by invocation of 
extensions which don't correctly release the GIL, effectively preventing *any* 
other Python threads from running (since the executing extension never releases it).

As an example, I frequently use SWIG to access hardware API's from Python. My 
standard 'exception translator' (which SWIG automatically places around every 
call to the extension) now looks something like:

%exception {
   Py_BEGIN_ALLOW_THREADS
   try {
     $action
   } except (...) {
     Py_BLOCK_THREADS
     SWIG_exception(SWIG_RuntimeError, "Unexpected exception")
   }
   Py_END_ALLOW_THREADS
}

The above means that every call into my extension releases the GIL 
automatically, and reacquires it when returning to Python. I usually don't call 
the Python C API from the extension, but if I did, I would need to reacquire the 
GIL with PyGILState_Ensure() before doing so.

Without those threading API calls in place, operations which access the hardware 
always block the entire program, even if the Python program is multi-threaded.

See here for some more info on Python's threading:
http://www.python.org/doc/2.4/api/threads.html

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at email.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://boredomandlaziness.skystorm.net



More information about the Python-list mailing list