QThread尝试通过PyGILState_Ensure()获取Python GIL时发生死锁

时间:2017-12-28 09:40:28

标签: c++ multithreading python-3.x qt python-embedding

我有一个C ++ / Qt应用程序,我想在其中嵌入Python解释器。我想从QThread调用Python,但是我在调​​用PyGILState_Ensure()的行中遇到死锁,以便尝试获取全局解释器锁(GIL)。

我将在下面提供一个简单而直接的示例,该示例遵循给出的here建议:

//main.cpp:
#include <QCoreApplication>
#include <QThread>
#include "Worker.h"

void startThread()
{
    QThread* thread = new QThread;
    Worker* worker = new Worker();
    worker->moveToThread(thread);
    QObject::connect(thread, SIGNAL(started()), worker, SLOT(process()));
    QObject::connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
    QObject::connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
    QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
    thread->start();
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Py_Initialize();
    startThread();
    Py_FinalizeEx();
    return a.exec();
}


//Worker.h:
#ifndef WORKER_H
#define WORKER_H

#include <QObject>
#include "Python.h"

class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = nullptr) : QObject(parent) {}

Q_SIGNALS:
    void finished();

public Q_SLOTS:
    void process()
    {
        qDebug("Calling Python");
        PyGILState_STATE gstate = PyGILState_Ensure();
        PyRun_SimpleString("print(\"hello\")");
        PyGILState_Release(gstate);
        qDebug("Done calling Python");
        Q_EMIT finished();
    }
};

#endif // WORKER_H

其他一些评论:

  • 注意:.pro文件包含行CONFIG += no_keywords,以避免名称与Python标题冲突。
  • 应该注意的是,当线程在调用PyGILState_Ensure()时停止执行时,主线程继续不受禁止。如果我将return a.exec();更改为return 0;,程序将退出。 (因此,使用死锁是错误的术语。)
  • 请注意,我对从Python中创建线程不感兴趣。我只想直接从QThread调用给定的Python脚本。
  • 我已经阅读了其他{​​{3}} similar,但觉得所考虑的案例略有不同,而且我无法从那里给出的答案中解决我的问题。此外,我对推荐PyEval_InitThreads()的建议感到困惑,如果我理解正确questions,则不应该这样做。

1 个答案:

答案 0 :(得分:0)

浏览了SO之后,我找到了解决方案。 this answer中的示例1特别有用。

我需要做的是从主线程中调用PyEval_InitThreads()the very cryptic documentation完全不清楚)。然后,要使PyGILState_Ensure()从其他线程中获取GIL(或者在Python源代码中陷入无限循环,不断尝试并且无法获取GIL),我需要释放GIL主线程通过调用PyEval_SaveThread()。最后,我应该通过调用PyEval_RestoreThread()再次检索主线程中的GIL(确保所有想要调用PyGILState_Ensure()的线程在此之前完成,否则再次冒险锁定和以前一样的原因)。

以下是解决此问题的main.cpp的更新版本:

#include <QCoreApplication>
#include <QThread>
#include "Worker.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Py_Initialize();

    //Initialize threads and release GIL:
    PyEval_InitThreads();
    PyThreadState *threadState;
    threadState = PyEval_SaveThread();

    QThread* thread = new QThread;
    Worker* worker = new Worker();
    worker->moveToThread(thread);
    QObject::connect(thread, SIGNAL(started()), worker, SLOT(process()));
    QObject::connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
    QObject::connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
    QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
    thread->start();

    //wait until thread is finished calling Python code:
    thread->wait(1000); //(ugly, but in a proper Qt application, this would be handled differently..)

    //Retrieve GIL again and clean up:
    PyEval_RestoreThread(threadState);
    Py_FinalizeEx();

    return a.exec();
}