Python、线程、GIL 和 C++

Posted

技术标签:

【中文标题】Python、线程、GIL 和 C++【英文标题】:Python, Threads, the GIL, and C++ 【发布时间】:2009-12-20 04:56:04 【问题描述】:

有没有办法让 boost::python 在每次与 python 交互时控制 Python GIL?

我正在用 boost::python 编写一个项目。我正在尝试为外部库编写 C++ 包装器,并使用 python 脚本控制 C++ 库。我无法更改外部库,只能更改我的包装程序。 (我正在为所述外部库编写功能测试应用程序)

外部库是用 C 编写的,并使用函数指针和回调来完成大量繁重的工作。它是一个消息传递系统,所以当有消息进来时,例如会调用一个回调函数。

我在我的库中实现了一种观察者模式,以便多个对象可以监听一个回调。我将所有主要玩家都正确导出,并且可以很好地控制到一定程度。

外部库创建线程来处理消息,发送消息,处理等。其中一些回调可能会从不同的进程中调用,我最近发现python不是线程安全的。

这些观察者可以在python中定义,所以我需要能够调用python并且python需要随时调用我的程序。

我这样设置对象和观察者

class TestObserver( MyLib.ConnectionObserver ):
    def receivedMsg( self, msg ):
        print("Received a message!")

ob = TestObserver()
cnx = MyLib.Conection()
cnx.attachObserver( ob )

然后我创建一个发送到连接的源并调用 receivedMsg 函数。

所以一个常规的 source.send('msg') 将进入我的 C++ 应用程序,进入 C 库,它将发送消息,连接将获取它,然后调用回调,这将返回到我的 C++库和连接试图通知所有的观察者,此时是这里的python类,所以它调用了那个方法。

当然回调是从连接线程调用的,而不是主应用程序线程。

昨天一切都崩溃了,我无法发送 1 条消息。然后在 Cplusplus-sig 档案中四处挖掘后,我了解了 GIL 和一些用于锁定内容的漂亮函数。

所以我的观察者类的 C++ python 包装器现在看起来像这样

struct IConnectionObserver_wrapper : Observers::IConnectionObserver, wrapper<Observers::IConnectionObserver>

    void receivedMsg( const Message* msg )
    
        PyGILState_STATE gstate = PyGILState_Ensure();
        if( override receivedMsg_func = this->get_override( "receivedMsg" ) )
            receivedMsg_func( msg );
        Observers::IConnectionObserver::receivedMsg( msg );
        PyGILState_Release( gstate );
    

但是,当我尝试发送超过 250 条这样的消息时,这很有效

for i in range(250)
    source.send('msg")

它再次崩溃。具有与以前相同的消息和症状,

PyThreadState_Get: no current thread

所以我想这次我在调用我的 C++ 应用程序时遇到问题,而不是调用 python。

我的问题是,有没有办法让 boost::python 为每次与 python 的交互处理 GIL 本身?我在代码中找不到任何东西,而且很难找到 source.send 调用进入 boost_python 的位置:(

【问题讨论】:

如果这尽可能简短,那么更长的版本会是什么?我想知道。无论如何,我认为如果将其分成多个问题,您将更有可能获得答案,尽管我不知道这是否可能,因为我还没有阅读您的问题。 哈,我知道有人会这么说,我发誓我尽量简短!这是一个非常复杂的问题,只有在你有全貌的情况下才能理解 它几乎没有尽可能简短。我刚刚删除了四段无内容填充文本。写长而复杂的问题时,尽量坚持重点。没有笑话或可爱的小旁白或任何可能分散注意力的东西。尽早给我们这个问题,而不是让我们在了解您的问题之前阅读半本小说。首先提问,然后填写所有详细信息。让我们轻松回答,或者至少知道我们是否能够回答。 【参考方案1】:

我在邮件列表上发现了一个非常不起眼的帖子,上面说要使用 PyEval_InitThreads(); 在 BOOST_PYTHON_MODULE 这实际上似乎停止了崩溃。

无论程序是否报告它收到的所有消息,它仍然是一个废话。如果我发送 2000,大多数情况下它会说收到 2000,但有时它报告的数量要少得多。

我怀疑这可能是由于线程同时访问我的计数器,所以我回答这个问题是因为这是一个不同的问题。

修复就行了

BOOST_PYTHON_MODULE(MyLib)

    PyEval_InitThreads();
    class_ stuff

【讨论】:

文档在这里,fwiw:docs.python.org/c-api/…【参考方案2】:

不完全了解您的问题,但请查看 CallPolicies:

http://www.boost.org/doc/libs/1_37_0/libs/python/doc/v2/CallPolicies.html#CallPolicies-concept

您可以定义新的调用策略(例如,一个调用策略是“return_internal_reference”),它将在执行包装的 C++ 函数之前和/或之后执行一些代码。我已经成功实现了一个调用策略,在执行 C++ 包装函数之前自动释放 GIL,并在返回 Python 之前再次获取它,所以我可以编写如下代码:

.def( "long_operation", &long_operation, release_gil<>() );

调用策略可能会帮助您更轻松地编写此代码。

【讨论】:

这是个好主意,肯定比一直调用确保释放更干净,谢谢! @Bruno 这不起作用,因为在预调用之后它返回到解释器进行一些类型转换。然后它崩溃了,因为 GIL 被释放了。使用指向其他问题的链接更新您的答案! @e.tadeu 是对的,我后来(显然)发现了一个关于 boost::python 在调用 precall 后进行类型转换的问题,导致解释器崩溃。可以在这里找到另一个同样易于使用的解决方案(感谢 e.tadeu 的帮助):***.com/questions/2135457/… @布鲁诺。您如何使用无状态调用策略实现 gil 发布?【参考方案3】:

我认为最好的方法是避免 GIL 并确保您与 python 的交互是单线程的。

目前我正在设计一个基于 boost.python 的测试工具,并认为我可能会使用生产者/消费者队列来调度来自多线程库的事件,这些事件将由 python 线程顺序读取。

【讨论】:

如果我无法正确解决问题,我会将其保留为一个选项,但在我看来,当时编写线程安全事件调度程序会比仅编辑 boost python 更复杂,即我最终做了什么。有一个名为 TxFox(或其他东西)的项目,它支持多线程 python,我拿了他的补丁,自己做了一些更改,现在我的 boost python 自己管理所有 GIL 东西,让库不用担心它。

以上是关于Python、线程、GIL 和 C++的主要内容,如果未能解决你的问题,请参考以下文章

Python中的GIL锁

python GIL锁

python GIL锁 锁 线程池 生产者消费模型

浅析Python的GIL和线程安全

10.1python中的GIL

python GIL锁