从C++线程中调用Python脚本,GIL

3

请帮助我..
我正在尝试从不同的C++线程调用Python脚本,但遇到了一些问题。

主函数:

Py_Initialize();    
PyEval_InitThreads();
PyThreadState *mainThreadState = PyThreadState_Get();
PyEval_ReleaseLock();
PyInterpreterState *mainInterpreterState = mainThreadState->interp;
...
//creating threads with myThreadState per thread
    PyEval_AcquireLock();
    PyThreadState *myThreadState = PyThreadState_New(mainInterpreterState);
    PyEval_ReleaseLock();
//running threads
...
PyEval_RestoreThread(mainThreadState);
Py_Finalize();

在线程对象中的run()函数:

PyEval_AcquireLock();
PyThreadState_Swap(m_threadState);
...
script = "f = open('file_for_this_thread','w')\n"   
         "print f\n"
         "f.write('111')\n"                     
         "print f.fileno()\n"
PyRun_SimpleString( script );
...
PyThreadState_Swap(NULL);
PyEval_ReleaseLock();

'print f'会显示每个文件的正确信息,但是有些地方出了问题,因为第二个 'print f' 会打印不同线程相同的输出,并且输出(如果有的话)将会写入一个文件而不是每个线程对应一个文件。
如果我用 time.sleep(1) 替换 f.write,那么文件处理器也会变成相等的。但是没有崩溃发生。
我也尝试过使用 PyGILState_Ensure/PyGILState_Release,但是效果一样。
主函数:
Py_Initialize();
PyEval_InitThreads();
PyThreadState*  mainThreadState = PyEval_SaveThread();
...
//creating and running threads
...
PyEval_RestoreThread(mainThreadState);
Py_Finalize();

储物柜:

TPyScriptThreadLocker:
    PyGILState_STATE m_state;
public:
    TPyScriptThreadLocker(): m_state(PyGILState_Ensure() {}
    ~TPyScriptThreadLocker() { PyGILState_Release(m_state); }

线程对象中的run()函数:

TPyScriptThreadLocker lock;
...
script = "f = open('file_for_this_thread','w')\n"   
         "print f.fileno()\n"
         "f.write('111')\n"                     
         "print f.fileno()\n"
PyRun_SimpleString( script );

我知道在大多数情况下,Python中的多线程不是好主意,但现在我想知道这段代码有什么问题...

Python 2.7
来源: http://www.linuxjournal.com/article/3641?page=0,2

源代码: http://files.mail.ru/9D4TEF pastebin: http://pastebin.com/DfFT9KN3


1
不好意思,如果这听起来像个愚蠢的问题,但是你如何定义文件名称?在你的代码中它只是说 'file_for_this_thread'。那个文件名是怎么确定的呢? - jogojapan
其实这只是一个快捷方式,每个线程都有唯一的文件路径,例如QString("d:\%1")。正确的文件出现了,第一个f.write正常工作,第二个则不行。如果在f.fileno输出之前插入time.sleep(1),也会发生相同的情况,即当2个不同的线程同时开始运行时(并发),我就明白了。 - Anton N.
是的,使用Qt,我会上传它。 - Anton N.
我怀疑发生的情况是,Python文件变量f被Python解释器视为全局变量,并在所有线程之间共享。毕竟,所有线程都使用同一个解释器实例。 - jogojapan
已将“f”重命名为“somefilename”,没有任何更改。 - Anton N.
显示剩余8条评论
1个回答

1

正如我在评论中分析的那样,问题是由于您代码中所有线程使用相同的Python解释器实例所致,该实例在此处被创建和初始化:

Py_Initialize();    

当第一个线程运行此处定义的脚本时:

script = "f = open('file_for_this_thread','w')\n"   
         "print f.fileno()\n"
         "f.write('111')\n"                     
         "print f.fileno()\n"

这会导致Python解释器分配一个全局Python变量f。不久之后,另一个线程会导致重新定义同一个全局变量。这可能在第一个print f.fileno()时还没有发生,但显然在第二个之前已经发生了。

解决方案是确保没有全局变量在线程之间共享(或在每个线程中使用不同的Python解释器实例,这将带来巨大的额外内存成本)。

由于您的代码中当前唯一的全局Python变量是f,因此在每个线程中使用不同的名称来命名f就足够了。随着您的代码变得更加复杂,最好定义一个Python函数,并将f(以及任何其他需要的变量)用作局部变量:

PyRun_SimpleString(
   "def myfunc(thread_no):\n"
   "    f = open('file_for_thread_%d' % thread_no,'w')\n"
   "    print f.fileno()\n"
   "    f.write('111')\n"               
   "    print f.fileno()\n"
 );

以上内容必须仅应用一次,并且在任何线程运行之前应用。

然后,在每个线程中,您只需执行以下操作:

PyRun_SimpleString(QString("myfunc(%d)\n",current_thread_no));

也就是说,线程只会调用Python函数,而f将成为一个局部变量。


尝试制作一个简单的测试项目,但由于它过于简单而遇到了问题 :) 谢谢。 - Anton N.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接