首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Python3中的PyEval_InitThreads :如何/何时调用它?(传奇继续令人恶心)

Python3中的PyEval_InitThreads :如何/何时调用它?(传奇继续令人恶心)
EN

Stack Overflow用户
提问于 2013-03-18 05:37:45
回答 7查看 12.2K关注 0票数 30

基本上,对于究竟什么时候应该调用,以及需要什么伴随的API调用,似乎存在着大量的混淆/模糊。不幸的是,正式Python文档非常模糊。关于这个话题,已经有关于堆栈溢出的几个问题了,事实上,我个人已经对这个主题进行了问了一个几乎相同的问题,所以如果这是一个重复,我不会感到特别惊讶;但是考虑到这个问题似乎没有明确的答案。(遗憾的是,我没有快速拨号的吉多·范罗苏姆( Guido Van Rossum )。)

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

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

好的,让我们从Python文档本身开始。Python3.2文档说:

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

所以我在这里的理解是:

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

所以常识告诉我们,任何创建线程的C扩展模块都必须调用PyEval_InitThreads(),然后释放全局解释器锁。好吧,看起来很简单。从表面上看,所需的只是以下代码:

代码语言:javascript
复制
PyEval_InitThreads(); /* initialize threading and acquire GIL */
PyEval_ReleaseLock(); /* Release GIL */

似乎很容易..。但是不幸的是,Python3.2docs已弃用。相反,我们应该使用PyEval_SaveThread来发布GIL:

PyThreadState* PyEval_SaveThread() 释放全局解释器锁(如果已经创建并且启用了线程支持),并将线程状态重置为NULL,返回以前的线程状态(非空)。如果已创建锁,则当前线程必须已获得锁。

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

代码语言:javascript
复制
PyEval_InitThreads();
PyThreadState* st = PyEval_SaveThread();

事实上,这正是这个堆叠溢出的答案所说的。除非我在实践中实际尝试了这一点,否则Python解释器在导入扩展模块时会立即出错。好的。

好了,现在我放弃了Python的官方文档,转而使用Google。因此,这个随机博客声称您需要从扩展模块中调用PyEval_InitThreads()。当然,文档声称PyEval_InitThreads()获得了GIL,实际上,揭示了它确实调用了内部函数take_gil(PyThreadState_GET());

所以PyEval_InitThreads()肯定买下了GIL。那么,我认为您绝对需要在调用PyEval_InitThreads()之后以某种方式释放GIL。但是怎么做呢? PyEval_ReleaseLock()被否决了,而PyEval_SaveThread()只是莫名其妙地错了.

好吧..。因此,也许出于目前无法理解的原因,C扩展模块不需要发布GIL。我试过了..。而且,正如预期的那样,一旦另一个线程试图获得GIL (使用PyGILState),程序就会挂起死锁。所以是的..。您确实需要在调用PyEval_InitThreads()之后释放GIL。

因此,问题是:在调用http://docs.python.org/3.2/c-api/init.html#PyEval_InitThreads之后,如何释放GIL?

更广泛地说:C扩展模块需要做什么才能安全地从工作C线程调用Python代码?

EN

回答 7

Stack Overflow用户

回答已采纳

发布于 2013-03-18 07:16:42

您的理解是正确的:调用PyEval_InitThreads可以获得GIL。在正确编写的Python/C应用程序中,这不是问题,因为GIL将被及时解锁,无论是自动的还是手动的。

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

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

当嵌入Python时,主线程通常初始化Python并继续执行其他与Python无关的任务。在这个场景中,没有任何东西会自动释放GIL,所以这必须由线程本身来完成。这并不是特定于调用PyEval_InitThreads的调用,而是期望使用GIL获得的调用来调用所有Python/C代码

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

代码语言:javascript
复制
Py_Initialize();
PyEval_InitThreads();

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

Py_Finalize();

如果您的代码手动创建线程,那么它们需要在执行任何与Python相关的操作之前获取GIL,甚至像Py_INCREF一样简单。要做到这一点,请使用以下是

代码语言:javascript
复制
// 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);
票数 16
EN

Stack Overflow用户

发布于 2017-03-08 09:36:06

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

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

编码如下。

代码语言:javascript
复制
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;
}
  1. 另一种方法是,我们可以在主线程中执行Python解释器,并且,对于每个线程,我们可以给出自己的子解释器。因此,每个线程都有自己独立的所有导入模块的独立版本,包括基本模块-- builtins、__main__和sys。

代码如下

代码语言:javascript
复制
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;
}

需要注意的是,Global解释器锁仍然存在,尽管为每个线程提供了单独的解释器,但是当涉及到python执行时,我们仍然只能一次执行一个线程。GIL unique to PROCESS,因此尽管为每个线程提供了唯一的子解释器,但我们不能同时执行线程。

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

多线程教程(msdn)

票数 8
EN

Stack Overflow用户

发布于 2015-06-17 10:20:50

我看到了类似于您的症状:如果我只调用PyEval_InitThreads(),就会出现死锁,因为我的主线程再也不会从Python中调用任何东西,如果我无条件地调用类似于PyEval_SaveThread()的东西,则会出现分段错误。症状取决于Python的版本和情况:我正在开发一个插件,将Python嵌入到一个库中,该库可以作为Python扩展的一部分加载。因此,代码需要独立运行,而不依赖于Python是否将其作为main加载。

下面的代码用于python2.7和python3.4,我的库运行在Python内部和Python外部。在我在主线程中执行的插件init例程中,我运行:

代码语言:javascript
复制
  Py_InitializeEx(0);
  if (!PyEval_ThreadsInitialized()) {
    PyEval_InitThreads();
    PyThreadState* mainPyThread = PyEval_SaveThread();
  }

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

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

代码语言:javascript
复制
  PyGILState_STATE gstate;
  gstate = PyGILState_Ensure();
  // Python C API calls
  PyGILState_Release(gstate);
票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/15470367

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档