【问题标题】:Python C Extension Nested Dictionary Segmentation FaultPython C 扩展嵌套字典分段错误
【发布时间】: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


    【解决方案1】:

    我相信我已经找到了分段错误的原因。后台线程正在修改解释器的状态,但没有获得全局解释器锁 (GIL)。这确实会导致解释器以意想不到的方式行事。

    为了解决这个问题,我首先在模块初始化函数中调用函数 PyEval_InitThreads()。接下来要做的是将使用 Python C API 与函数 PyGILState_Ensure() 和 PyGILState_Release() 的后台线程中的任何指令封装起来。以下是经过此修复的修改后的源代码。

    #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);
    }
    

    扩展程序现在运行时没有任何分段错误。

    【讨论】:

      猜你喜欢
      • 2019-11-23
      • 1970-01-01
      • 2018-08-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-16
      • 1970-01-01
      • 2010-12-13
      • 2018-11-26
      相关资源
      最近更新 更多