我正在尝试在C中创建一个python(2.7.12(扩展名,该扩展可以执行以下操作:
- 提供一个仅读取的嵌套词典,具有Python程序员的模块级别范围。
- Python程序员将不可见的背景线程将在字典中添加,删除和修改条目。
- 扩展名将直接构建到Python解释器中。
我创建了此扩展名的简化版本,该版本将一个条目添加到字典中,然后不断用新值对其进行修改。以下是C文件,其中包含有关它正在做什么的评论以及我的理解如何处理参考计数。
#include <Python.h>
#include <pthread.h>
static PyObject *module;
static PyObject *pyitem_error;
static PyObject *item;
static PyObject *item_handle;
static pthread_t thread;
void *stuff(void *param)
{
int garbage = 0;
PyObject *size;
PyObject *value;
while(1)
{
// Build a dictionary called size containg two integer objects
// Py_BuildValue will pass ownership of its reference to size to this thread
size = NULL;
size = Py_BuildValue("{s:i,s:i}", "l", garbage, "w", garbage);
if(size == NULL)
{
goto error;
}
// Build a dictionary containing an integer object and the size dictionary
// Py_BuildValue will create and own a reference to the size dictionary but not steal it
// Py_BuildValue will pass ownership of its reference to value to this thread
value = NULL;
value = Py_BuildValue("{s:i,s:O}", "h", garbage, "base", size);
if(value == NULL)
{
goto error;
}
// Add the new data to the dictionary
// PyDict_SetItemString will borrow a reference to value
PyDict_SetItemString(item, "dim", value);
error:
Py_XDECREF(size);
Py_XDECREF(value);
garbage++;
}
return NULL;
}
// There will be methods for this module in the future
static PyMethodDef pyitem_methods[] =
{
{NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC initpyitem(void)
{
// Create a module object
// Own a reference to it since Py_InitModule returns a borrowed reference
module = Py_InitModule("pyitem", pyitem_methods);
Py_INCREF(module);
// Create an exception object for future use
// Own a second reference to it since PyModule_AddObject will steal a reference
pyitem_error = PyErr_NewException("pyitem.error", NULL, NULL);
Py_INCREF(pyitem_error);
PyModule_AddObject(module, "error", pyitem_error);
// Create a dictionary object and a proxy object that makes it read only
// Own a second reference to the proxy object since PyModule_AddObject will steal a reference
item = PyDict_New();
item_handle = PyDictProxy_New(item);
Py_INCREF(item_handle);
PyModule_AddObject(module, "item", item_handle);
// Start the background thread that modifies the dictionary
pthread_create(&thread, NULL, stuff, NULL);
}
以下是使用此扩展程序的Python程序。它所做的就是打印出词典中的内容。
import pyitem
while True:
print pyitem.item
print
此扩展名似乎工作了一段时间,然后在分段故障的情况下崩溃。对核心转储的检查揭示了以下内容:
Core was generated by `python pyitem_test.py'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 PyObject_Malloc (nbytes=nbytes@entry=42) at Objects/obmalloc.c:831
831 if ((pool->freeblock = *(block **)bp) != NULL) {
[Current thread is 1 (Thread 0x7f144a824700 (LWP 3931))]
这个核心转储使我相信这个问题可能与我处理对象参考计数有关。我相信这可能是一个原因,因为其他人带来的核心转储提出的问题通过正确处理参考计数解决了问题。但是,我对对象参考计数的处理没有任何错误。
想到的另一件事是,Python中的打印功能可能仅借用对字典内容的引用。当它试图打印字典(或以任何其他方式访问其内容(时,背景线程会出现并用新的条目代替旧条目。这会导致旧条目的参考计数减少,然后由垃圾收集器删除对象。但是,打印功能仍在尝试使用导致错误的旧引用。
我发现有趣的是,我可以通过仅更改字典中的键名来更改扩展的速度或缓慢的速度。
有人对问题有什么见解?有没有更好的方法来创建扩展名,并且仍然具有我想要的属性?
我相信我发现了分割故障的原因。背景线程正在修改解释器的状态,而无需获得全局解释器锁(GIL(。这确实会导致口译员以意想不到的方式行事。
要解决此问题,我首先在模块初始化函数中调用函数pyeval_initthreads((。接下来要做的是将使用Python C API与功能pygilstate_ensure((和pygilstate_release((((使用Python c api((。以下是此修复程序的修改源代码。
#include <Python.h>
#include <pthread.h>
static PyObject *module;
static PyObject *pyitem_error;
static PyObject *item;
static PyObject *item_handle;
static pthread_t thread;
void *stuff(void *param)
{
int garbage = 0;
PyObject *size;
PyObject *value;
PyGILState_STATE state; // Needed for PyGILState_Ensure() and PyGILState_Release()
while(1)
{
// Obtain the GIL
state = PyGILState_Ensure();
size = NULL;
size = Py_BuildValue("{s:i,s:i}", "l", garbage, "w", garbage);
if(size == NULL)
{
goto error;
}
value = NULL;
value = Py_BuildValue("{s:i,s:O}", "h", garbage, "base", size);
if(value == NULL)
{
goto error;
}
PyDict_SetItemString(item, "dim", value);
error:
Py_XDECREF(size);
Py_XDECREF(value);
// Release the GIL
PyGILState_Release(state);
garbage++;
}
return NULL;
}
static PyMethodDef pyitem_methods[] =
{
{NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC initpyitem(void)
{
module = Py_InitModule("pyitem", pyitem_methods);
Py_INCREF(module);
pyitem_error = PyErr_NewException("pyitem.error", NULL, NULL);
Py_INCREF(pyitem_error);
PyModule_AddObject(module, "error", pyitem_error);
item = PyDict_New();
item_handle = PyDictProxy_New(item);
Py_INCREF(item_handle);
PyModule_AddObject(module, "item", item_handle);
// Initialize Global Interpreter Lock (GIL)
PyEval_InitThreads();
pthread_create(&thread, NULL, stuff, NULL);
}
现在,扩展名在没有任何分割故障的情况下运行。