【问题标题】:Concurrency: Are Python extensions written in C/C++ affected by the Global Interpreter Lock?并发:用 C/C++ 编写的 Python 扩展是否受全局解释器锁的影响?
【发布时间】:2010-10-13 15:45:53
【问题描述】:

Python 的强项之一是易于编写 C 和 C++ 扩展以加速代码的处理器密集型部分。这些扩展可以避免 Global Interpreter Lock 还是它们也受到 GIL 的限制?如果不是,那么这个“易于扩展”比我之前意识到的更具有杀手锏。我怀疑答案不是简单的是或否,但我不确定,所以我在 StackOverflow 上问这个问题。

【问题讨论】:

    标签: c++ python c multithreading


    【解决方案1】:

    是的,对 C 扩展的调用(从 Python 调用的 C 例程)仍受 GIL 约束。

    但是,您可以手动在 C 扩展中释放 GIL,只要在将控制权返回给 Python VM 之前小心地重新声明它即可。

    有关信息,请查看 Py_BEGIN_ALLOW_THREADSPy_END_ALLOW_THREADS 宏:http://docs.python.org/c-api/init.html#thread-state-and-the-global-interpreter-lock

    【讨论】:

    【解决方案2】:

    Python 的 C/C++ 扩展不受 GIL 约束。但是,您确实需要知道自己在做什么。 From http://docs.python.org/c-api/init.html:

    全局解释器锁用于保护指向当前线程状态的指针。释放锁并保存线程状态时,必须在释放锁之前检索当前线程状态指针(因为另一个线程可以立即获取锁并将自己的线程状态存储在全局变量中)。反之,在获取锁和恢复线程状态时,必须先获取锁,然后再存储线程状态指针。

    为什么我要讲这么多细节?因为当从 C 语言创建线程时,它们没有全局解释器锁,也没有线程状态数据结构。这样的线程必须通过首先创建线程状态数据结构,然后获取锁,最后存储它们的线程状态指针来引导自己存在,然后才能开始使用 Python/C API。完成后,他们应该重置线程状态指针,释放锁,最后释放他们的线程状态数据结构。

    【讨论】:

    • 您是否正在考虑嵌入 Python 的C 代码,而不是C 扩展?当 Python 解释器中的一个线程调用你的 C 扩展时,它仍然持有 GIL,除非你特别释放它。您引用的部分是在讨论完全在外部 Python 创建的线程。
    • 我猜 OP 是模棱两可的。您可以在不受 GIL 约束的 C 扩展中旋转线程。
    【解决方案3】:

    看看 Cython,它的语法与 Python 相似,但有一些结构,如“cdef”、快速 numpy 访问函数和“with nogil”语句(按照它所说的那样做)。

    【讨论】:

      【解决方案4】:

      如果您使用 C++ 编写扩展程序,则可以使用 RAII 轻松、清晰地编写操作 GIL 的代码。我使用这对 RAII 结构体:

      namespace py {
      
          namespace gil {
      
              struct release {
                  PyThreadState* state;
                  bool active;
      
                  release()
                      :state(PyEval_SaveThread()), active(true)
                      {}
      
                  ~release() { if (active) { restore(); } }
      
                  void restore() {
                      PyEval_RestoreThread(state);
                      active = false;
                  }
              };
      
              struct ensure {
                  PyGILState_STATE* state;
                  bool active;
      
                  ensure()
                      :state(PyGILState_Ensure()), active(true)
                      {}
      
                  ~ensure() { if (active) { restore(); } }
      
                  void restore() {
                      PyGILState_Release(state);
                      active = false;
                  }
              };
      
          }
      
      }
      

      ... 允许为给定块切换 GIL(以任何上下文管理器 Pythonista 粉丝可能似乎不太熟悉的语义方式):

      PyObject* YourPythonExtensionFunction(PyObject* self, PyObject* args) {
      
          Py_SomeCAPICall(…);     /// generally, if it starts with Py* it needs the GIL
          Py_SomeOtherCall(…);    /// ... there are exceptions, see the docs
      
          {
              py::gil::release nogil;
              std::cout << "Faster and less block-y I/O" << std::endl
                        << "can run inside this block -" << std::endl
                        << "unimpeded by the GIL";
          }
      
          Py_EvenMoreAPICallsForWhichTheGILMustBeInPlace(…);
      
      }
      

      ...确实,我个人也发现扩展 Python 的便利性,以及对内部结构和状态的控制级别,这是一个杀手级功能。

      【讨论】:

      • 注:如果您使用任何这些 API 调用,您首先需要在尝试任何手动 GIL 控制之前对PyEval_InitThreads() 进行初始调用(例如在您的模块初始化函数中)
      猜你喜欢
      • 2016-07-28
      • 1970-01-01
      • 2016-08-26
      • 1970-01-01
      • 1970-01-01
      • 2012-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多