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

Sum*_*Tea 2 c++ python multithreading gil

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

下面是我的数据订阅者的简化的可运行代码示例.代码main.py,其中模块proc代表处理器,订阅ZeroMQ套接字localhost:10000,设置回调,并通过调用将接收的消息发送到处理器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()
Run Code Online (Sandbox Code Playgroud)

模块proc简化,导出三个功能:

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

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

代码有什么问题吗?我怀疑GIL会干扰消息处理器中pthread的并发性,或者gevent循环被饿死了.有关该问题或如何调试它的任何提示将非常感谢!

Kev*_*vin 9

全局解释器锁本身不会阻止线程被调度.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(),这意味着你封锁了整个解释.

这里指定了这些规则的正式版本.密切关注"非Python创建的线程"部分; 这正是你要做的事情.

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