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

时间:2010-11-11 09:54:30

标签: c++ python c multithreading

以下是在多线程程序中使用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(参见docs, p.#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 个答案:

答案 0 :(得分:4)

当CPython调用可能阻塞(或重新进入Python)的函数时,它会在调用函数之前释放全局解释器锁,然后在函数返回后重新获取锁。在您的代码中,您调用内置print函数会导致释放解释器锁并运行另一个线程(请参阅stringobject.c中的string_print)。

所以你需要自己的锁:全局解释器锁不适合确保执行I / O的Python代码的序列化。

由于您正在使用Boost线程框架,因此您可能会发现使用Boost thread synchronization primitives之一最方便,例如boost::interprocess::interprocess_mutex

[编辑:我的原始回答是错误的,正如Abyx所指出的那样。]

答案 1 :(得分:2)

with语句在Python 3.1中有issue,但它已在Python 3.2和Python 2.7中修复。

所以正确的解决方案是使用threading模块进行同步。

为避免此类问题,不应使用在globals字典中使用临时变量的多线程代码,或者为每个线程使用不同的全局字典。