【发布时间】:2018-02-18 02:36:12
【问题描述】:
我正在尝试在 C 中创建一个 Python (2.7.12) 扩展,它执行以下操作:
- 为 Python 程序员提供具有模块级范围的只读嵌套字典。
- Python 程序员不可见的后台线程将添加、删除和修改字典中的条目。
- 该扩展将直接内置到 Python 解释器中。
我创建了这个扩展的一个简化版本,它在字典中添加一个条目,然后不断地用新值修改它。下面是 C 文件,其中包含有关它正在做什么以及我对如何处理引用计数的理解的 cmets。
#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 中的 print 函数可能只是借用了对字典内容的引用。当它试图打印字典(或以任何其他方式访问其内容)时,后台线程会出现并用新条目替换旧条目。这会导致旧条目的引用计数减少,然后垃圾收集器会删除该对象。但是,打印功能仍在尝试使用导致错误的旧引用。
我发现有趣的是,我可以通过仅更改字典中键的名称来更改扩展程序出现分段错误的速度或速度。
是否有人对问题可能有任何见解?有没有更好的方法来创建扩展并且仍然拥有我想要的属性?
【问题讨论】:
标签: python dictionary segmentation-fault reference-counting