这发生在Python 3.4.3
来自非python创建的线程:
https://docs.python.org/3.4/c-api/init.html#non-python-created-threads
在SO上有几个问题可以从C / C ++应用程序中查看类似的问题。
AssertionError (3.X only) when calling Py_Finalize with threads
Fatal error during Py_Finalize in embedded Python application
PyEval_InitThreads in Python 3: How/when to call it? (the saga continues ad nauseum)
但是没有一个专门处理Grand Central Dispatch。我不知道它是否重要,因为它可能只是引擎盖下的线程。
然而,试图从这些帖子中应用知识仍然会引起我的问题。
所以这就是我目前所处的位置,我有一个代表我的Python Runtime的obj-c类,我有以下相关方法:
- (void)initialize
{
Py_Initialize();
PyEval_InitThreads();
PyObject* sysPath = PySys_GetObject((char*)"path");
for(NSString * path in self.pythonPath){
PyList_Append(sysPath, objc_convert_string(path));
}
// not calling PyEval_SaveThread
// causes beginTask below to die on
// PyEval_AcquireThread
// self.threadState = PyThreadState_Get();
// release the GIL, this shouldn't need to be
// done, as I understand it,
// but as the comment above states, if I don't
// beginTask will fail at PyEval_AcquireThread
self.threadState = PyEval_SaveThread();
self.running = YES;
}
这就是我初始化Python的方式。然后我通过以下方式调用python命令:
- (void)beginTask:(nonnull void (^)(void))task completion:(nullable void (^)(void))completion
{
dispatch_async(self.pythonQueue, ^{
PyInterpreterState * mainInterpreterState = self.threadState->interp;
PyThreadState * myThreadState = PyThreadState_New(mainInterpreterState);
PyEval_AcquireThread(myThreadState);
// Perform any Py_* related functions here
task();
PyEval_ReleaseThread(PyThreadState_Get());
if (completion){
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
}
在执行长时间运行操作的情况下,我有一些代码可以完成Jinja模板的一些代码,从而保存到文件系统。我想在完成后清理,所以我打电话给Py_Finalize
然后重新初始化,使用上面的方法输入我的问题:
- (void)finalize
{
PyEval_RestoreThread(self.threadState);
// Problems here
Py_Finalize();
self.running = NO;
}
这会导致Python中出现以下错误:
Exception ignored in: <module 'threading' from '/Users/blitz6/Library/Developer/Xcode/DerivedData/Live-cblwxzmbsdpraeebpregeuhikwzh/Build/Products/Debug/LivePython.framework/Resources/python3.4/lib/python3.4/threading.py'>
Traceback (most recent call last):
File "/Users/blitz6/Library/Developer/Xcode/DerivedData/Live-cblwxzmbsdpraeebpregeuhikwzh/Build/Products/Debug/LivePython.framework/Resources/python3.4/lib/python3.4/threading.py", line 1296, in _shutdown
_main_thread._delete()
File "/Users/blitz6/Library/Developer/Xcode/DerivedData/Live-cblwxzmbsdpraeebpregeuhikwzh/Build/Products/Debug/LivePython.framework/Resources/python3.4/lib/python3.4/threading.py", line 1019, in _delete
del _active[get_ident()]
KeyError: 140735266947840
我尝试过几种不同的方法,包括在PyGILState_Ensure()
中使用PyGILState_Release(myState);
和beginTask
:
- (void)beginTask:(nonnull void (^)(void))task completion:(nullable void (^)(void))completion
{
dispatch_async(self.pythonQueue, ^{
PyGILState_STATE state = PyGILState_Ensure();
task();
PyGILState_Release(state);
if (completion){
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
}
但是这会导致上面的finalize
方法出现此错误:
Exception ignored in: <module 'threading' from '/Users/blitz6/Library/Developer/Xcode/DerivedData/Live-cblwxzmbsdpraeebpregeuhikwzh/Build/Products/Debug/LivePython.framework/Resources/python3.4/lib/python3.4/threading.py'>
Traceback (most recent call last):
File "/Users/blitz6/Library/Developer/Xcode/DerivedData/Live-cblwxzmbsdpraeebpregeuhikwzh/Build/Products/Debug/LivePython.framework/Resources/python3.4/lib/python3.4/threading.py", line 1289, in _shutdown
assert tlock.locked()
AssertionError:
所以我几乎坚持:如何Py_Finalize
没有出现某种错误。显然,我不理解某些事情。我也可以通过xcode确认,当我点击断点时,我的dispatch_async
块正在从另一个不是主线程的线程运行。
更新
修补一下,我发现了,这个:
PyObject* module = PyImport_ImportModule("requests");
当我在另一个线程上时,在我Py_Finalize
Exception ignored in: <module 'threading' from '/Users/blitz6/Library/Developer/Xcode/DerivedData/Live-cblwxzmbsdpraeebpregeuhikwzh/Build/Products/Debug/LivePython.framework/Resources/python3.4/lib/python3.4/threading.py'>
Traceback (most recent call last):
File "/Users/blitz6/Library/Developer/Xcode/DerivedData/Live-cblwxzmbsdpraeebpregeuhikwzh/Build/Products/Debug/LivePython.framework/Resources/python3.4/lib/python3.4/threading.py", line 1296, in _shutdown
_main_thread._delete()
File "/Users/blitz6/Library/Developer/Xcode/DerivedData/Live-cblwxzmbsdpraeebpregeuhikwzh/Build/Products/Debug/LivePython.framework/Resources/python3.4/lib/python3.4/threading.py", line 1019, in _delete
del _active[get_ident()]
KeyError: 140735266947840
如果我导入:
PyObject* module = PyImport_ImportModule("os");
OR
PyObject* module = PyImport_ImportModule("json");
一切都运行得很好。当我开始导入自己的模块时,我遇到了问题。
在Py_Finalize
内,wait_for_thread_shutdown();
是我遇到此问题的地方。我想,根据评论,它与:
/ *等到threading._shutdown完成,提供 首先导入了线程模块。 关闭例程将等到所有非守护进程 “线程”线程已经完成。 * /
具体在wait_for_thread_shutdown
:
PyThreadState *tstate = PyThreadState_GET();
PyObject *threading = PyMapping_GetItemString(tstate->interp->modules,
"threading");
if (threading == NULL) {
/* threading not imported */
PyErr_Clear();
return;
}
tstate
返回 NULL ,但threading
不是 NULL ,它会跳过PyErr_Clear()
代码路径并执行:
result = _PyObject_CallMethodId(threading, &PyId__shutdown, "");
if (result == NULL) {
PyErr_WriteUnraisable(threading);
}
else {
Py_DECREF(result);
}
Py_DECREF(threading);
如果我只是:
PyObject* module = PyImport_ImportModule("json");
然后,PyErr_WriteUnraisable(threading);
在wait_for_thread_shutdown
result = _PyObject_CallMethodId(threading, &PyId__shutdown, "");
if (result == NULL) {
PyErr_WriteUnraisable(threading);
}
答案 0 :(得分:1)
如前所述,我正在考虑我的代码而我正在考虑嵌入式Python解释器错误。它与GCD无关,而且与我对嵌入式Python的某些方面的误解有关。我可能仍然有误解,但下面的逻辑符合我对我认为应该做的事情的期望,我的错误消失了。
我试图做的是执行一次性任务,而不是保留任何东西。认为我需要在这里穿线是让我沮丧的原因。
我正在做的是获取GIL,然后导入一些使用threading
模块的python代码。当你这样做时,解释器会注册你引入了线程模块,就像你Py_Finalize
它跳过一些箍时一样,以确保你可能存在或不存在的所有子线程都被关闭。我实际上正在从它下面拉出地毯,这导致了我的错误。相反,我所做的工作更有利于Py_NewInterpreter
。当您致电threading
但与自身隔离时,它会与Py_Finalize
完全相同的Py_EndInterpreter
关机程序。
所以我的最终GCD一次性完成代码如下:
<强>初始化强>
- (void)initialize
{
Py_Initialize();
PyEval_InitThreads();
[self updateSysPath];
// Release the GIL
self.threadState = PyEval_SaveThread();
self.running = YES;
}
任务执行
- (void)beginTask:(nonnull void (^)(Runtime * __nonnull))task completion:(nullable void (^)(Runtime * __nonnull))completion
{
dispatch_async(self.pythonQueue, ^{
PyInterpreterState * mainInterpreterState = self.threadState->interp;
PyThreadState * taskState = PyThreadState_New(mainInterpreterState);
// Acquire the GIL
PyEval_AcquireThread(taskState);
PyThreadState* tstate = Py_NewInterpreter();
[self updateSysPath];
task(self);
// when Py_EndInterpreter is called, the current
// thread state is set to NULL,
// so we need to put ourselves back on
// the taskState, and release the GIL
Py_EndInterpreter(tstate);
PyThreadState_Swap(taskState);
// release the GIL
PyEval_ReleaseThread(taskState);
// cleanup
PyThreadState_Clear(taskState);
PyThreadState_Delete(taskState);
if (completion){
dispatch_async(dispatch_get_main_queue(), ^{
completion(self);
});
}
});
}
<强>最后确定强>
- (void)finalize
{
// acquire the GIL
PyEval_RestoreThread(self.threadState);
Py_Finalize();
self.running = NO;
}
答案 1 :(得分:0)
你说:
但是没有一个专门处理Grand Central Dispatch。一世 不知道它是否重要,因为它可能只是线程 引擎盖
是的,最终,GCD块必须在一个OS线程或另一个OS线程上执行,但GCD队列不在单个线程之上的抽象,并且(主队列除外)make根本没有关于线程亲和力的承诺。换句话说,没有办法保证提交的块在同一个线程或任何特定的线程上执行,也不明智地对它们最终执行的线程做出任何假设。
最安全,最直接的方法可能是显式管理操作系统线程(通过NSThread
或pthreads
API)计划用于运行Python代码。 GCD中的线程管理在一天结束时是一个私有的实现细节,这意味着即使你设法得到现在有效的东西,Apple也可以对GCD中的线程管理进行一些微妙的改动,并打破你的应用程序。将来
总之,GCD队列可能不适合这项工作。
我最近发布了another answer describing a way to manage a private thread for wrapping a third-party library that demanded thread affinity。你可能会感兴趣。