带有 Python 嵌入的 `threading.local` 意外行为

问题描述

我正在使用 C 嵌入 API 嵌入 Python。主线程做

Py_Initialize();
PyEval_InitThreads();

然后我有更多由本机代码创建的线程,它们的生命周期我无法控制。他们也需要调用 Python。到目前为止,他们似乎与

一起工作得很好
void* gil = PyGILState_Ensure();

calls to Python go here

PyGILState_Release(gil);

问题在于这个简单的设置,我在使用 threading.local 的 Python 代码时遇到了问题。想象一下辅助线程 S,它定期执行 increase_counter

// initialized once at the beginning of program to
// an instance of threading.local()
PyObject* threadLocal;

...

void increase_counter()
{

void* gil = PyGILState_Ensure();

// this really does C API calls,but for simplicity I'll write Python
if hasattr(threadLocal,"counter"):
  threadLocal.counter += 1
else:
  threadLocal.counter = 1
// end of Python

PyGILState_Release(gil);

}

嗯,问题是在线程 S 中多次调用 increase_counter 实际上并没有增加任何东西 - hasattr 总是返回 False,而 {{ 的值一旦调用 counter,该线程的 1}} 就会被丢弃。

只有在 PyGILState_Release 的整个主体都被包裹成以下内容时,它才能在 S 中正常工作:

S

针对这个问题我可以做的,但是在实际产品中void* gilForS = PyGILState_Ensure(); void* sPythonThreadState = PyEval_SaveThread(); // rest of the S body,that sometimes calls increase_counter PyEval_RestoreThread(sPythonThreadState); PyGILState_Release(gilForS); 的生命周期不是我控制的(它是一个线程池线程),只有S,所以我不能让它在开始时运行 increase_counter,我不能确保 PyEval_SaveThread 最终会被调用

初始化像 PyEval_RestoreThread 这样的线程以便 S 在那里正常工作的正确方法是什么?

完整示例,按要求重现问题。打印两次“设置计数器”而不是“设置计数器”+“找到计数器!”。如果我取消注释 threading.local 中的代码,这在实际产品中无法执行,它会按预期工作:

async_thread

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)