[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