[Python-Dev] Making python C-API thread safe (try 2)

Harri Pesonen fuerte at sci.fi
Tue Sep 16 13:06:00 EDT 2003


Brian Quinlan wrote:
>>The point I was trying to make (not in this message but in general) is
>>that it would be simple (trivial but tedious) to create a version of
>>Python that is thread-safe, and the only reason it is not done is
>>because it would break old code. 
> 
> Actually, it would be reasonably difficult. If you don't agree why not
> spend a weekend implementing it and see how robust your implementation
> is? Also, adding object-level locking would involve a huge performance
> penalty. 

There is no object-level locking in my proposal. Just independent 
free-threaded interpreters, which don't see the objects of other 
interpreters at all.

There could be an extra global interpreter state for shared-memory 
object access. Accessing this would always be synchronized, but only 
this. Python would automatically copy data from this state to 
thread-local state and back when needed. This would require a special 
syntax for variables in global state:

synchronize a = "asdf"

>>Only one global variable left (in fact there is Py_None as well). Why
>>not get rid of it, then??
> 
> I think that you must be missing something... There is nothing special
> about Py_None. The number 5 is globally shared just like Py_None is.
> This is a performance optimization used to prevent lots of small numbers
> from being allocated and destroyed all the time.

Py_None is special because it is shared between all interpreters, it is 
global. Py_None is defined as:

#define Py_None (&_Py_NoneStruct)

PyObject _Py_NoneStruct = {
	PyObject_HEAD_INIT(&PyNone_Type)
};

static PyTypeObject PyNone_Type = {
	PyObject_HEAD_INIT(&PyType_Type)
	0,
	"NoneType",
	0,
	0,
	(destructor)none_dealloc,	     /*tp_dealloc*/ /*never called*/
	0,		/*tp_print*/
	0,		/*tp_getattr*/
	0,		/*tp_setattr*/
	0,		/*tp_compare*/
	(reprfunc)none_repr, /*tp_repr*/
	0,		/*tp_as_number*/
	0,		/*tp_as_sequence*/
	0,		/*tp_as_mapping*/
	0,		/*tp_hash */
};

 From the above we see that when Py_None reference count reaches zero, a 
destructor none_dealloc is called.

static void
none_dealloc(PyObject* ignore)
{
	/* This should never get called, but we also don't want to SEGV if
	 * we accidently decref None out of existance.
	 */
	Py_FatalError("deallocating None");
}

This makes no sense at all. Why Py_FatalError? It would be better to have

static void
none_dealloc(PyObject* ignore)
{
}

so that it is not necessary to call Py_INCREF(Py_None) 
Py_DECREF(Py_None) at all. Guess how many times these are called in 
Python C API? Py_INCREF is called 2001 times and Py_DECREF two times.

OK, Python also calls _Py_ForgetReference on the object when it is 
freed, so that something else should be changed here as well. 
_Py_ForgetReference unlinks the object from double linked list.

By changing the way how Py_None is freed (by doing nothing) Python would 
get simpler and faster. And of course you could give Py_None a large 
reference count on startup, so that the deallocator is never actually 
called (even if it does nothing).

If there are more of these static objects, like number 5 as you said, 
then the destructor for these should be changed as well, so that they 
are never freed. This way the independent free-threaded interpreters 
don't have to worry about these objects.

Harri





More information about the Python-list mailing list