在多线程程序中同步嵌入的Python

3 投票
2 回答
2427 浏览
提问于 2025-04-16 06:54

下面是一个在多线程程序中使用Python解释器的例子:

#include <python.h>
#include <boost/thread.hpp>

void f(const char* code)
{
    static volatile auto counter = 0;
    for(; counter < 20; ++counter)
    {
        auto state = PyGILState_Ensure();
        PyRun_SimpleString(code);
        PyGILState_Release(state);

        boost::this_thread::yield();
    }
}

int main()
{
    PyEval_InitThreads();
    Py_Initialize();
    PyRun_SimpleString("x = 0\n");
    auto mainstate = PyEval_SaveThread();

    auto thread1 = boost::thread(f, "print('thread #1, x =', x)\nx += 1\n");
    auto thread2 = boost::thread(f, "print('thread #2, x =', x)\nx += 1\n");
    thread1.join();
    thread2.join();

    PyEval_RestoreThread(mainstate);
    Py_Finalize();
}

看起来没问题,但它并没有同步。Python解释器在执行PyRun_SimpleString时,会多次释放和重新获取全局解释器锁(GIL)(详细信息可以查看文档,第2页)。

我们可以通过使用自己的同步对象来序列化PyRun_SimpleString的调用,但这样做是不对的。

Python有自己的同步模块——_threadthreading。但是在这段代码中它们并不起作用:

Py_Initialize();
PyRun_SimpleString(R"(
import _thread
sync = _thread.allocate_lock()

x = 0
)");

auto mainstate = PyEval_SaveThread();

auto thread1 = boost::thread(f, R"(
with sync:
    print('thread #1, x =', x)
    x += 1
)");
  • 会出现错误File "<string>", line 3, in <module> NameError: name '_[1]' is not defined,并且会导致死锁。

那么,如何以最有效的方式同步嵌入的Python代码呢?

2 个回答

4

当CPython调用一个可能会阻塞(或者重新进入Python)的函数时,它会在调用这个函数之前释放全局解释器锁,然后在函数返回后再重新获取这个锁。在你的代码中,是你调用内置的print函数导致了解释器锁被释放,从而让其他线程可以运行(可以查看string_printstringobject.c中的实现)。

所以你需要自己创建一个锁:全局解释器锁并不适合用来确保执行I/O操作的Python代码是按顺序进行的。

因为你在使用Boost线程框架,所以使用Boost提供的一些线程同步工具会比较方便,比如boost::interprocess::interprocess_mutex

[编辑:我之前的回答是错误的,感谢Abyx的指正。]

2

with 语句在 Python 3.1 中有一个 问题,但在 Python 3.2 和 Python 2.7 中已经修复了。

所以正确的做法是使用 threading 模块来进行同步。

为了避免这种问题,不应该使用多线程代码,这种代码如果在全局字典中使用临时变量,或者为每个线程使用不同的全局字典。

撰写回答