Python 3中的PyEval_InitThreads:如何/何时调用它?(传奇继续令人作呕)

Cha*_*l72 20 python python-c-api python-c-extension python-3.x python-3.2

基本上似乎存在大量混淆/模糊性,而不PyEval_InitThreads()应该在什么时候被调用,以及需要哪些API调用.遗憾的是,官方Python文档非常模糊.关于这个主题的stackoverflow已经有很多问题了,事实上,我个人已经问了一个与这个问题几乎完全相同的问题,所以如果这个问题以复制方式结束,我不会特别感到惊讶; 但是考虑到这个问题似乎没有明确的答案.(可悲的是,我没有Guido Van Rossum的快速拨号.)

首先,让我们在这里定义问题的范围:我想做什么? 嗯...我想在C中编写一个Python扩展模块,它将:

  1. pthread在C中使用API的Spawn工作线程
  2. 从这些C线程中调用Python回调

好的,让我们从Python文档开始吧.在Python的3.2文档说:

void PyEval_InitThreads()

初始化并获取全局解释器锁.它应该在创建第二个线程或参与任何其他线程操作(如PyEval_ReleaseThread(tstate))之前在主线程中调用.在调用PyEval_SaveThread()或PyEval_RestoreThread()之前不需要它.

所以我的理解是:

  1. 产生线程的任何C扩展模块必须PyEval_InitThreads()在生成任何其他线程之前从主线程调用
  2. 调用PyEval_InitThreads锁定GIL

所以常识告诉我们,任何创建线程的C扩展模块都必须调用PyEval_InitThreads(),然后释放Global Interpreter Lock.好吧,看起来很简单.所以初步,所有的需要将以下代码:

PyEval_InitThreads(); /* initialize threading and acquire GIL */
PyEval_ReleaseLock(); /* Release GIL */
Run Code Online (Sandbox Code Playgroud)

看起来很简单......但不幸的是,Python 3.2文档PyEval_ReleaseLock已经弃用了.相反,我们应该使用PyEval_SaveThread以释放GIL:

PyThreadState*PyEval_SaveThread()

释放全局解释器锁(如果已创建并启用了线程支持)并将线程状态重置为NULL,则返回先前的线程状态(不是NULL).如果已创建锁,则当前线程必须已获取它.

呃......好吧,我想C扩展模块需要说:

PyEval_InitThreads();
PyThreadState* st = PyEval_SaveThread();
Run Code Online (Sandbox Code Playgroud)


实际上,这正是这个stackoverflow回答所说的.除非我在实践中尝试这样做,否则当我导入扩展模块时,Python解释器会立即出现错误.尼斯.


好的,现在我放弃官方的Python文档并转向谷歌.所以,这个随机的博客声称你需要做的就是扩展模块PyEval_InitThreads().当然,文档声称PyEval_InitThreads()取得GIL,而事实上,一对源代码的快速检查PyEval_InitThreads()ceval.c发现,它确实调用内部函数take_gil(PyThreadState_GET());

所以PyEval_InitThreads() 一定要收购GIL.我会认为你在调用之后绝对需要以某种方式释放GIL PyEval_InitThreads().   但是怎么样? PyEval_ReleaseLock()不推荐使用,PyEval_SaveThread()只是莫名其妙的段错误.

好的...所以也许由于某些原因,目前我的理解不多,C扩展模块不需要发布GIL.我尝试了......并且,正如预期的那样,一旦另一个线程尝试获取GIL(使用PyGILState_Ensure),程序就会从死锁中挂起.所以是的...你真的需要在打电话后释放GIL PyEval_InitThreads().

所以再一次,问题是:你怎么在打电话后释放GIL PyEval_InitThreads()

更一般地说:C-extension模块究竟需要做什么才能从工作者C线程安全地调用Python代码?

use*_*342 12

你的理解是正确的:PyEval_InitThreads除其他外,调用确实获得了GIL.在正确编写的Python/C应用程序中,这不是问题,因为GIL将自动或手动解锁.

如果主线程继续运行Python代码,没有什么特别的事情要做,因为Python解释器会在执行了许多指令后自动放弃GIL(允许另一个线程获取它,这将再次放弃它,所以上).此外,每当Python即将调用阻塞系统调用时,例如,从网络读取或写入文件,它将在呼叫周围释放GIL.

这个答案的原始版本在这里结束了.但还有一件事需要考虑:嵌入场景.

嵌入Python时,主线程通常会初始化Python并继续执行其他非Python相关的任务.在那种情况下,没有什么会自动释放GIL,所以这必须由线程本身完成.这绝不是特定于调用的调用PyEval_InitThreads,它应该是所有使用GIL调用的Python/C代码.

例如,main()可能包含如下代码:

Py_Initialize();
PyEval_InitThreads();

Py_BEGIN_ALLOW_THREADS
... call the non-Python part of the application here ...
Py_END_ALLOW_THREADS

Py_Finalize();
Run Code Online (Sandbox Code Playgroud)

如果您的代码手动创建线程,他们需要在执行任何与Python相关的操作之前获取GIL ,即使这很简单Py_INCREF.为此,请使用以下内容:

// Acquire the GIL
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();

... call Python code here ...

// Release the GIL. No Python API allowed beyond this point.
PyGILState_Release(gstate);
Run Code Online (Sandbox Code Playgroud)

  • 但是,正如我在问题中提到的,如果*主线程*持有GIL(它将在它调用`PyEval_InitThreads()之后),那么当工作线程调用`PyGILState_Ensure`时,它将会死锁. (2认同)
  • 对于downvote真的很好奇.一些解释? (2认同)
  • @user4815162342 - 如果调用 PyEval_InitThreads 的线程实际上没有对 Python 做任何事情,它就会死锁。它发生在某种类型的 Init 调用中,因此此时不一定需要调用 Python。那么该线程是否只是继续其业务而忽略程序执行其余部分的 GIL 锁? (2认同)
  • @user4815162342 关于你的问题“为什么会死锁?”:据我所知,尝试检索 GIL 的 `PyGILState_Ensure()` 会死锁(特别是,在函数 `take_gil()` 中陷入无限循环在 Python 源代码中)如果 GIL 不可检索。为了使其可检索,在调用“PyEval_InitThreads()”之后,调用该函数的代码还必须调用“PyEval_SaveThread()”。(此外,`PyEval_RestoreThread()` 将再次使 GIL 无法通过 `PyGILState_Ensure()` 检索。) (2认同)

ash*_*lam 6

执行 C/Python API 时有两种多线程方法。

1.使用相同的解释器执行不同的线程 - 我们可以执行一个 Python 解释器并在不同的线程上共享相同的解释器。

编码如下。

main(){     
//initialize Python
Py_Initialize();
PyRun_SimpleString("from time import time,ctime\n"
    "print 'In Main, Today is',ctime(time())\n");

//to Initialize and acquire the global interpreter lock
PyEval_InitThreads();

//release the lock  
PyThreadState *_save;
_save = PyEval_SaveThread();

// Create threads.
for (int i = 0; i<MAX_THREADS; i++)
{   
    hThreadArray[i] = CreateThread
    //(...
        MyThreadFunction,       // thread function name
    //...)

} // End of main thread creation loop.

// Wait until all threads have terminated.
//...
//Close all thread handles and free memory allocations.
//...

//end python here
//but need to check for GIL here too
PyEval_RestoreThread(_save);
Py_Finalize();
return 0;
}

//the thread function

DWORD WINAPI MyThreadFunction(LPVOID lpParam)
{
//non Pythonic activity
//...

//check for the state of Python GIL
PyGILState_STATE gilState;
gilState = PyGILState_Ensure();
//execute Python here
PyRun_SimpleString("from time import time,ctime\n"
    "print 'In Thread Today is',ctime(time())\n");
//release the GIL           
PyGILState_Release(gilState);   

//other non Pythonic activity
//...
return 0;
}
Run Code Online (Sandbox Code Playgroud)
  1. 另一种方法是,我们可以在主线程中执行一个 Python 解释器,并且我们可以为每个线程提供自己的子解释器。因此,每个线程都使用所有导入模块的自己独立的独立版本运行,包括基本模块 - 内置模块、__main__ 和 sys。

代码如下

int main()
{

// Initialize the main interpreter
Py_Initialize();
// Initialize and acquire the global interpreter lock
PyEval_InitThreads();
// Release the lock     
PyThreadState *_save;
_save = PyEval_SaveThread();


// create threads
for (int i = 0; i<MAX_THREADS; i++)
{

    // Create the thread to begin execution on its own.

    hThreadArray[i] = CreateThread
    //(...

        MyThreadFunction,       // thread function name
    //...);   // returns the thread identifier 

} // End of main thread creation loop.

  // Wait until all threads have terminated.
WaitForMultipleObjects(MAX_THREADS, hThreadArray, TRUE, INFINITE);

// Close all thread handles and free memory allocations.
// ...


//end python here
//but need to check for GIL here too
//re capture the lock
PyEval_RestoreThread(_save);
//end python interpreter
Py_Finalize();
return 0;
}

//the thread functions
DWORD WINAPI MyThreadFunction(LPVOID lpParam)
{
// Non Pythonic activity
// ...

//create a new interpreter
PyEval_AcquireLock(); // acquire lock on the GIL
PyThreadState* pThreadState = Py_NewInterpreter();
assert(pThreadState != NULL); // check for failure
PyEval_ReleaseThread(pThreadState); // release the GIL


// switch in current interpreter
PyEval_AcquireThread(pThreadState);

//execute python code
PyRun_SimpleString("from time import time,ctime\n" "print\n"
    "print 'Today is',ctime(time())\n");

// release current interpreter
PyEval_ReleaseThread(pThreadState);

//now to end the interpreter
PyEval_AcquireThread(pThreadState); // lock the GIL
Py_EndInterpreter(pThreadState);
PyEval_ReleaseLock(); // release the GIL

// Other non Pythonic activity
return 0;
}
Run Code Online (Sandbox Code Playgroud)

需要注意的是,全局解释器锁仍然存在,尽管为每个线程提供了单独的解释器,但在执行python时,我们仍然一次只能执行一个线程。GILPROCESS唯一的,所以尽管为每个线程提供了唯一的子解释器,但我们不能同时执行线程

资料来源:在主线程中执行一个 Python 解释器,对于每个线程,我们可以给它自己的子解释器

多线程教程 (msdn)


小智 5

我见过类似的症状:如果仅调用PyEval_InitThreads(),则会死锁,因为我的主线程再也不会从Python调用任何东西;如果我无条件地调用类似PyEval_SaveThread()的事件,则会出现segfaults。症状取决于Python的版本和情况:我正在开发一个插件,该插件将Python嵌入到可作为Python扩展的一部分加载的库中。因此,代码需要独立于Python是否作为主要代码来运行。

以下代码适用于python2.7和python3.4,以及我的库在Python内和Python外运行。在主线程中执行的插件初始化例程中,运行:

  Py_InitializeEx(0);
  if (!PyEval_ThreadsInitialized()) {
    PyEval_InitThreads();
    PyThreadState* mainPyThread = PyEval_SaveThread();
  }
Run Code Online (Sandbox Code Playgroud)

(mainPyThread实际上是一些静态变量,但我认为这并不重要,因为我不再需要再次使用它)。

然后,我使用pthreads创建线程,并在需要访问Python API的每个函数中使用:

  PyGILState_STATE gstate;
  gstate = PyGILState_Ensure();
  // Python C API calls
  PyGILState_Release(gstate);
Run Code Online (Sandbox Code Playgroud)

  • 我想正确的方法是最终通过“PyEval_RestoreThread(mainPyThread)”在“Py_FinalizeEx()”之前的某个地方再次检索主线程中的GIL。确保在执行此操作之前等待所有线程完成执行,否则任何线程中对 `PyGILState_Ensure()` 的任何调用都将死锁(Python 源代码中的函数 `take_gil()` 内的无限循环),因为GIL 现在不再可用。 (2认同)