Python3:来自多线程 C 共享库的 c 型段错误

问题描述 投票:0回答:1

我查看了其他 StackOverflow 相关问题和答案,并遵循了给出的信息。我仍然遇到麻烦。

在 Linux 上运行 Python3,访问共享 C 库 (.so)。 C 库将根据需要启动线程。在线程运行时访问 C API 时,我遇到分段错误问题。仅当 C 线程调用 Python 中的回调函数时,才会出现此问题。如果 C 线程在不调用回调的情况下运行,则没有问题。在下面的代码中,Python 行

print(GetData(128))
将获取字符串并将其打印到控制台,然后导致段错误。同样,如果线程正在运行并调用事件或回调,我只会在
GetData
上遇到段错误。其他
C
API 返回
char*
,但不带参数,可以在线程运行时正常工作。

Python:

# Within a class, but not shown for simplicity.
EVENT = CFUNCTYPE(c_void_p, c_int)
lib = CDLL("library.so")
lib.GetData.restype = c_uint
lib.GetData.argtypes = [c_char_p, c_uint]
lib.SetEvent.argtypes = [EVENT]

def GetData(count):
    buf = create_string_buffer(count)
    lib.GetData(buf, count)
    return buf.value.decode("utf-8")

def SetEvent(ev):
    lib.SetEvent(ev)

# Code outside of class.
def Event(e):
    print("Event " + str(e))
    if e == 2:
        print(GetData(128)) # Event works fine until I call this.

SetEvent(EVENT(Event))

cnt = 0
while cnt < 10:
    print(GetData(128)) # Works if I do not call SetEvent.
    cnt = cnt + 1
    sleep(1)

C/C++ 代码:

typedef void(*Event)(int i);
Event efunc = nullptr;

static void ThreadFunc()
{
    int i = 0;

    while(true)
    {
        if (efunc != nullptr) { efunc(++i); } // Raise event.

        if (i > 10) { i = 0; }

        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

extern "C" unsigned int GetData(char* data, unsigned int count)
{
    const char* str = "Random data for testing";
    std::strcpy(data, str);
    return std::strlen(str);
}

extern "C" void SetEvent(Event e)
{
    efunc = e;

    if (efunc != nullptr) { myThread = std::thread(ThreadFunc); }
}

我在上面放置了简单版本的代码,以使问题更容易理解。我知道缺少错误检查并且线程永远运行。实际代码中并非如此,但如果我添加所有内容,这篇文章就会太长。另外,

C
库需要创建线程,这是无法改变的。当动态加载并使用
C
C++
运行时,相同的
C#
库可以工作。

python c multithreading segmentation-fault shared-libraries
1个回答
0
投票

鉴于代码在 C++ 和 C# 中运行良好,很可能是 Python 的全局解释器锁 (GIL) 导致了该问题。解决此问题的一种方法是在调用 Python 回调之前获取 C 代码中的 GIL,然后释放它。在 Python C API 中,这可以使用 PyGILState_Ensure 和 PyGILState_Release 来完成。

如果您不想修改 C/C++ 库,另一种解决方法是使用 Python 的 ctypes 库在调用回调之前和之后在 Python 中手动获取和释放 GIL。

© www.soinside.com 2019 - 2024. All rights reserved.