Python 中的子解释器没有很好的文档记录,甚至没有得到很好的支持。以下是我所不理解的。它似乎在实践中运作良好。
在处理 Python 中的线程和子解释器时,需要了解两个重要概念。首先,Python 解释器并不是真正的多线程。它有一个全局解释器锁 (GIL),需要获取它才能执行几乎所有 Python 操作(此规则有一些罕见的例外)。
其次,线程和子解释器的每个组合都必须有自己的线程状态。解释器为它管理的每个线程创建一个线程状态,但是如果你想从不是由该解释器创建的线程中使用 Python,你需要创建一个新的线程状态。
首先你需要创建子解释器:
初始化 Python
Py_Initialize();
初始化 Python 线程支持
如果您计划从多个线程调用 Python,则需要)。此调用还获取 GIL。
PyEval_InitThreads();
保存当前线程状态
我本可以使用 PyEval_SaveThread(),但它的副作用之一是释放 GIL,然后需要重新获取。
PyThreadState* _main = PyThreadState_Get();
创建子解释器
PyThreadState* ts1 = Py_NewInterpreter();
PyThreadState* ts2 = Py_NewInterpreter();
恢复主解释线程状态
PyThreadState_Swap(_main);
我们现在有两个子解释器的线程状态。这些线程状态仅在创建它们的线程中有效。每个想要使用其中一个子解释器的线程都需要为该线程和解释器的组合创建一个线程状态。
在新线程中使用子解释器
这是一个示例代码,用于在不是由子解释器创建的新线程中使用子解释器。新线程必须获取 GIL,为线程和解释器组合创建新的线程状态,并使其成为当前线程状态。最后必须进行相反的清理。
void do_stuff_in_thread(PyInterpreterState* interp)
{
// acquire the GIL
PyEval_AcquireLock();
// create a new thread state for the the sub interpreter interp
PyThreadState* ts = PyThreadState_New(interp);
// make ts the current thread state
PyThreadState_Swap(ts);
// at this point:
// 1. You have the GIL
// 2. You have the right thread state - a new thread state (this thread was not created by python) in the context of interp
// PYTHON WORK HERE
// release ts
PyThreadState_Swap(NULL);
// clear and delete ts
PyThreadState_Clear(ts);
PyThreadState_Delete(ts);
// release the GIL
PyEval_ReleaseLock();
}
在新线程中使用子解释器(Python 3.3 后)
之前的 do_stuff_in_thread() 仍然适用于所有当前的 Python 版本。但是,Python 3.3 弃用了PyEval_AcquireLock()/PyEval_ReleaseLock(),这导致了一些难题。
唯一记录在案的释放 GIL 的方法是调用 PyEval_ReleaseThread() 或 PyEval_SaveThread(),这两者都需要线程状态,而清理和删除当前线程状态需要保持 GIL。这意味着可以释放 GIL 或清理线程状态,但不能两者兼而有之。
幸运的是,有一个解决方案——PyThreadState_DeleteCurrent() 删除当前线程状态,然后释放 GIL。 [这个 API 从 3.9 开始才被记录,但它至少从 Python 2.7 开始就存在了]
修改后的do_stuff_in_thread() 也适用于所有当前的 Python 版本。
void do_stuff_in_thread(PyInterpreterState* interp)
{
// create a new thread state for the the sub interpreter interp
PyThreadState* ts = PyThreadState_New(interp);
// make it the current thread state and acquire the GIL
PyEval_RestoreThread(ts);
// at this point:
// 1. You have the GIL
// 2. You have the right thread state - a new thread state (this thread was not created by python) in the context of interp
// PYTHON WORK HERE
// clear ts
PyThreadState_Clear(ts);
// delete the current thread state and release the GIL
PyThreadState_DeleteCurrent();
}
现在每个线程都可以执行以下操作:
线程1
do_stuff_in_thread(ts1->interp);
线程2
do_stuff_in_thread(ts1->interp);
线程3
do_stuff_in_thread(ts2->interp);
调用Py_Finalize() 会破坏所有子解释器。或者,它们可以手动销毁。这需要在主线程中完成,使用创建子解释器时创建的线程状态。最后使主解释器线程状态为当前状态。
// make ts1 the current thread state
PyThreadState_Swap(ts1);
// destroy the interpreter
Py_EndInterpreter(ts1);
// make ts2 the current thread state
PyThreadState_Swap(ts2);
// destroy the interpreter
Py_EndInterpreter(ts2);
// restore the main interpreter thread state
PyThreadState_Swap(_main);
我希望这能让事情更清楚一些。
我在github 和另一个also on github 上有一个用C++ 编写的小完整示例(Python 3.3 后的变体)。