C++、Python 混合编程中多线程的 GIL 问题

作者: , 共 2185 字 , 共阅读 0

我们知道, Python 程序有全局锁,任何时候都只有一个 Python 语句在执行。在 Python 中,这通过全局的 GIL 锁来控制。当 C++和 Python 混合编程,且使用多线程时,也必须考虑到 GIL 锁(单线程无需考虑)。

一个 C++和 Python 的多线程混合编程的例子如下:

#include <python3.12/Python.h>
#include <thread>

void python_function() {
    PyGILState_STATE state = PyGILState_Ensure();

    // do something, includes call python objects and functions

    PyGILState_Release(state);
}

int main() {
    Py_Initialize();

    PyThreadState* state = PyEval_SaveThread();

    std::thread t(python_function);
    t.join();

    PyEval_RestoreThread(state);

    // do other thing, includes call python objects and functions
}

为什么要这么写?主要的就是为了获取和释放 GIL 全局锁:

  • Py_Initialize 会初始化 Python 解析器,此时主线程会获取到 GIL 锁。
  • PyEval_SaveThread() 会保存主线程状态,同时释放 GIL
  • 子线程里 PyGILState_Ensure 获取全局锁,开始执行 Python 代码。
  • 子线程执行完毕之后,使用 PyGILState_Release 释放 GIL。
  • 主线程通过 PyEval_RestoreThread 重新恢复线程状态,获取 GIL ,后续又可以跑 Python 代码了。

我们可以用 RAII 技术定义两个辅助类:

/**
 * @brief Python的全局GIL自动锁。该对象在构建时,可自动获取GIL锁,在销毁时,自动释放GIL全局锁。
 */
class Lock
{
   private:
    PyGILState_STATE state;  //!< @brief 全局GIL锁。
   public:
    /// @brief 自动锁上。 
    Lock() { state = PyGILState_Ensure(); }
    /// @brief 自动解锁。     
    ~Lock() { PyGILState_Release(state); }
};

/**
 * @brief 线程状态管理器。
 *
 * 在 c++ 中嵌入 python 解释器、并使用 c++ 库创建多线程来执行 python 代码的场景中,
 * 为了确保 python 解释器安全的切换线程,需要跟踪管理 c++ 所创建线程的状态。 
 * 该对象在构建时,会自动释放GIL并保存当前线程的状态,然后可切换到其他线程执行python代码。  
 * 该对象在销毁时,会重新获取GIL并恢复当前线程的状态,然后当前线程可以继续执行其他python代码。    
 * 由于使用了 RAII 技术,请使用时正确指定该对象的作用域,使析构函数在正确位置被调用。
 */
class PyThreadStateMgr
{
   private:
    PyThreadState* state;  //!< @brief python 线程状态。   
   public:
    /// @brief 释放 GIL,并自动保存线程当前状态 
    PyThreadStateMgr() { state = PyEval_SaveThread(); }

    /// @brief 重新获取 GIL,并自动恢复线程状态。 
    ~PyThreadStateMgr() { PyEval_RestoreThread(state); }
};

这样在使用时就很方便了:

#include <python3.12/Python.h>
#include <thread>

void python_function() {
    Lock _;

    // do something, includes call python objects and functions
}

int main() {
    Py_Initialize();

    {
        PyThreadStateMgr _;

        std::thread t(python_function);
        t.join(); 
    }

    // do other thing, includes call python objects and functions
}

Q. E. D.

类似文章:
编程 » Python, 并行计算
核心就是threading.Thread
编程 » C++
有两种方法,一种在线程的调用函数内部设置,还有一种是在外部对指定线程变量做设置。
std::thread是 C++ 11 新引入的标准线程库。在同样是 C++ 11 新引入的 lambda 函数的辅助下,std::thread用起来特别方便:
编程 » C++, 异步
C++11 的标准异步库至少包含下面内容:
看到网上有片段,提到没有必要自己实现自旋锁,因为标准库的 std::mutex 和现在的自旋锁的实现没有两样。比较好奇,翻了一些资料,试图找到答案。
相似度: 0.085
以下对并行计算的个人理解受到较多质疑,删除之。
编程 » Python
今天写一段程序时遇到一个问题,查了好一会才搞清楚。代码可以简化为下面这个小代码:
智能指针在现代 C++里用得越多。以前只知道它大致的原理,比如使用引用计数。但很多实现细节并不清楚,最直接的,它是如何实现多线程安全的?今天找了 gnu c++ library 的实现好好看了一下。
由 Facebook 开发和维护的 C++库 Folly 提供了自旋锁的实现folly::MicroSpinLock,代码文件地址:https://github.com/facebook/folly/blob/master/folly/synchronization/MicroSpinLock.h
编程 » pandas, numpy, Python, C++
首先任意定义一个结构,注意不要用 std::string 非平凡布局的变量,用 char[] 代替:
千灵山离城区很近,之前几次去戒台寺,都有看见这座山,但一直没去爬。这次终于走了一次。
周末走了一趟从大营盘到样边长城的穿越路线。我们 3 台车,采取两边放车的方式,从大营盘村出发,走到样边长城后下山,总里程 14 公里,爬升约 500 米。