使用多线程C ++扩展时,是否需要注意Python GIL?

时间:2016-05-29 19:21:08

标签: python c++ multithreading gil

我现在正在使用Python实现数据订阅者,订阅数据发布者(实际上是ZeroMQ发布者套接字),并在收到任何新消息后收到通知。在我的订户中,消息在收到后被转储到数据处理器。订户完成后也会收到订户的通知。由于数据处理器是用C ++编写的,因此我必须使用简单的C ++模块扩展Python代码。

下面是我的数据订阅者的简化可运行代码示例。代码main.py,其中模块proc代表处理器,订阅localhost:10000上的ZeroMQ套接字,设置回调,并通过调用proc.onMsg将收到的消息发送到处理器。

#!/bin/python
# main.py

import gevent
import logging
import zmq.green as zmq

import pub 
import proc

logging.basicConfig( format='[%(levelname)s] %(message)s', level=logging.DEBUG )

SUB_ADDR = 'tcp://localhost:10000'

def setupMqAndReceive():
    '''Setup the message queue and receive messages.
    '''
    ctx  = zmq.Context()
    sock = ctx.socket( zmq.SUB )
    # add topics
    sock.setsockopt_string( zmq.SUBSCRIBE, 'Hello' )

    sock.connect( SUB_ADDR )

    while True:
        msg = sock.recv().decode( 'utf-8' )
        proc.onMsg( msg )

def callback( a, b ):
    print( '[callback]',  a, b ) 

def main():
    '''Entrance of the module.
    '''
    pub.start()
    proc.setCallback( callback )
    '''A simple on-liner
    gevent.spawn( setupMqAndReceive ).join()
    works. However, the received messages will not be
    processed by the processor.
    '''
    gevent.spawn( setupMqAndReceive )
    proc.start()

简化了模块proc,导出了三个函数:

  • setCallback设置了回调函数,以便在处理邮件时,我的订阅者可以收到通知;
  • onMsg由订阅者调用;
  • start设置一个新的工作线程来处理来自订阅者的消息,并使主线程加入以等待工作线程退出。

可以在github https://github.com/more-more-tea/python_gil找到完整版源代码。然而,它并没有像我的期望那样运行。添加处理器线程后,订户无法从gevent循环中的发布者接收数据。如果我只是丢弃数据处理器模块,则订户gevent循环可以接收来自发布者的消息。

代码有什么问题吗?我怀疑GIL会干扰消息处理器中pthread的并发性,或者gevent循环被饿死了。任何关于该问题或如何调试它的提示都将受到高度赞赏!

2 个答案:

答案 0 :(得分:8)

Global Interpreter Lock本身不会阻止调度线程。 Python C API不会随处运行到pthread库中。这既好又坏。

这很好,因为你可以在C或C ++扩展中一次做多个事情。

这很糟糕,因为你可能会意外违反GIL规则。

GIL的规则(大致)如下:

  1. 当您从Python调用代码时,您可能认为您的线程具有GIL。当您从非Python的任何东西调用代码时,您可能不会做出这样的假设。
  2. 除非另有明确说明,否则您必须拥有GIL才能调用Python / C API的任何部分。这包括Python / C API拥有的所有,甚至包括引用宏Py_INCREF()Py_DECREF()等简单内容。
  3. 执行在C或C ++函数内时,GIL不会自动释放。如果您不需要GIL,则需要手动执行此操作。特别是,当您调用阻塞函数pthread_join()select()时,它不会自动释放,这意味着您会阻止整个解释器。
  4. 指定这些规则的正式版本here。密切关注"非Python创建的线程"部分;这正是你想要做的事情。

    阅读您的代码,看起来您未能在procThread()函数中获取GIL,并且在调用pthread_join()之前也无法释放它。可能还有其他问题,但对我来说这些问题最为明显。

答案 1 :(得分:1)

我有解决问题的方法和我对Python线程的理解和pthread原生的。

Python线程虽然受GIL保护,但实际上是系统线程。唯一让它们与众不同的是,在运行时,Python线程受GIL保护。由threading.Thread生成的线程是Python线程,并且在这些线程中运行的所有代码都由GIL自动保护。如果本机线程与Python线程共存并且Python线程将要运行阻塞语句,例如,Python线程中的GIL必须与Py_BEGIN_ALLOW_THREADSPy_END_ALLOW_THREADS一起发布。 I / O,Thread.join,sleep等等。

虽然其他线程在Python世界之外产生,例如通过pthread库,应该在执行Python代码时使用Python C API PyGILState_EnsurePyGILState_Release显式获取GIL(对于纯C / C ++代码,不需要根据我的经验获取Python GIL)在Kevin的回答中。

更新的代码可以在GitHub找到。

如果有任何误解,请给我评论。谢谢大家!