我查看了其他 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#
库可以工作。
鉴于代码在 C++ 和 C# 中运行良好,很可能是 Python 的全局解释器锁 (GIL) 导致了该问题。解决此问题的一种方法是在调用 Python 回调之前获取 C 代码中的 GIL,然后释放它。在 Python C API 中,这可以使用 PyGILState_Ensure 和 PyGILState_Release 来完成。
如果您不想修改 C/C++ 库,另一种解决方法是使用 Python 的 ctypes 库在调用回调之前和之后在 Python 中手动获取和释放 GIL。