2014-09-26 79 views
12

我正在玩Python的C API,但很难理解一些特殊情况。我可以测试它,但它似乎是一个容易出错和耗时的问题。所以我来这里看看是否有人已经这样做了。Python多线程多语言解释器C API

现在的问题是,这是用子解释器管理多线程的正确方法,线程和子解释器之间没有直接关系?

Py_Initialize(); 
PyEval_InitThreads(); /* <-- needed? */ 
_main = PyEval_SaveThread(); /* <-- acquire lock? does it matter? */ 
/* maybe do I not need it? */ 
i1 = Py_NewInterpreter(); 
i2 = Py_NewInterpreter(); 

我使用互斥锁吗?需要使用锁吗?螺纹功能应该是类似以下内容:(螺纹是非蟒蛇,大概POSIX线程)

线程1

_save = PyThreadState_Swap(i1); 
    // python work 
PyThreadState_Restore(_save); 

线程2(几乎相同)

_save = PyThreadState_Swap(i1); 
    // python work 
PyThreadState_Restore(_save); 

主题3(几乎相同,但与子翻译i2

_save = PyThreadState_Swap(i2); 
    // python work 
PyThreadState_Restore(_save); 

这是正确的吗?这是我想达到的一般情况吗?有没有比赛条件?

谢谢!

回答

14

Python中的子解释器没有很好的文档记录,甚至没有很好的支持。以下是对我未知的最好的。它在实践中似乎运作良好。

在Python中处理线程和子解释器时,Threre是理解两个重要的概念。首先,Python解释器并不是真正的多线程。它有一个全球解释器锁(GIL),需要获取它来执行几乎所有的Python操作(这个规则有少数例外)。

其次,线程和子解释器的每个组合都必须有它自己的线程状态。解释器为它管理的每个线程创建一个线程状态,但是如果你想从非解释器创建的线程使用Python,则需要创建一个新的线程状态。

首先,你需要创建子解释:

初始化的Python

Py_Initialize(); 

初始化Python的线程支持

,如果你打算从多个线程调用Python需要)。这个电话也获得了GIL。

PyEval_InitThreads(); 

保存当前线程状态

我可以使用PyEval_SaveThread(),但其副作用之一释放GIL,然后需要重新获取。

PyThreadState* _main = PyThreadState_Get(); 

创建子解释

PyThreadState* ts1 = Py_NewInterpreter(); 
PyThreadState* ts2 = Py_NewInterpreter(); 

恢复主解释线程状态

PyThreadState_Swap(_main); 

我们现在有子口译两个纱线的状态。这些线程状态仅在创建它们的线程中有效。每个想要使用其中一个子解释器的线程都需要为该线程和解释器的组合创建一个线程状态。

使用从一个新的线程

这里的子解释为在不通过副解释创建新线程使用子解释的示例代码。新线程必须获取GIL,为线程创建新的线程状态并解释组合,并使其成为当前的线程状态。最后必须做相反的事情来清理。

void do_stuff_in_thread(PyInterpreterState* interp) 
{ 
    // acquire the GIL 
    PyEval_AcquireLock(); 

    // create a new thread state for the the sub interpreter interp 
    PyThreadState* ts = PyThreadState_New(ts1->interp); 

    // make ts the current thread state 
    PyThreadState_Swap(ts); 

    // at this point: 
    // 1. You have the GIL 
    // 2. You have the right thread state - a new thread state (this thread was not created by python) in the context of interp 

    // PYTHON WORK HERE 

    // release ts 
    PyThreadState_Swap(NULL); 

    // clear and delete ts 
    PyThreadState_Clear(ts); 
    PyThreadState_Delete(ts); 

    // release the GIL 
    PyEval_ReleaseLock(); 
} 

现在,每个线程都可以做到以下几点:

线程1

do_stuff_in_thread(ts1->interp); 

线程2

do_stuff_in_thread(ts1->interp); 

Thread3

do_stuff_in_thread(ts2->interp); 

调用Py_Finalize()销毁所有的子解释器。或者,可以手动销毁。这需要在主线程中完成,使用创建子解释器时创建的线程状态。最后让主解释器线程声明当前状态。

// make ts1 the current thread state 
PyThreadState_Swap(ts1); 
// destroy the interpreter 
Py_EndInterpreter(ts1); 

// make ts2 the current thread state 
PyThreadState_Swap(ts2); 
// destroy the interpreter 
Py_EndInterpreter(ts2); 

// restore the main interpreter thread state 
PyThreadState_Swap(_main); 

我希望这可以让事情更清楚一点。

我有一个用C++编写的小型完整示例github

+0

谢谢!这正是我所寻找的,“详细的快速启动” - 比快速详细:)。如果它不适合你,我肯定会错过“线程和解释器组合的线程状态”。 – MariusSiuram 2014-10-27 13:54:40

+2

所以总结一下:有没有办法使用真正并行的多个python解释器实例(多核上的硬件线程)? – japedo 2015-07-08 13:50:56

+1

正确。 Python使用全局解释器锁,它允许一次只允许一个线程运行实际的Python代码。但是,执行长操作的C代码通常会释放锁,直到它返回到Python,以便可以执行另一个线程。这意味着实际使用将取决于您的代码。 – sterin 2015-07-10 01:30:48