QThread尝试通过PyGILState_Ensure()获取Python GIL时出现死锁 [英] Deadlock when QThread tries to acquire Python GIL via PyGILState_Ensure()

查看:525
本文介绍了QThread尝试通过PyGILState_Ensure()获取Python GIL时出现死锁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个C ++/Qt应用程序,我想在其中嵌入Python解释器.我想从QThread调用Python,但是在尝试获取全局解释器锁(GIL)的行PyGILState_Ensure()处出现了死锁.

I have a C++/Qt application in which I want to embed the Python interpreter. I want to call Python from a QThread, but I'm getting a deadlock at the line where I call PyGILState_Ensure() in order to try to acquire the global interpreter lock (GIL).

我将在下面提供一个简单明了的示例,该示例遵循

I'll provide a minimal and straight-forward example below, which follows the recommendations given 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

一些其他评论:

  • Note: The .pro file contains the line CONFIG += no_keywords, in order to avoid name conflicts with the Python header.
  • It should be noted that while the thread halts execution at the call to PyGILState_Ensure(), the main thread continues uninhibited. If I change return a.exec(); to return 0;, the program will exit. (So perhaps deadlock is the wrong term to use.)
  • Please note that I'm not interested in creating threads from within Python. I just want to straight-forwardly call a given Python script from a QThread.
  • I've read other similar questions, but felt that the cases considered there are subtly different, and I've not been able to solve my problem from the answers given there. Also, I'm confused by the recommendations to call PyEval_InitThreads(), which if I understand the Python/C API documentation correctly, shouldn't be needed.

推荐答案

浏览了SO之后,我找到了一个解决方案. 此答案中的示例1特别有用.

After browsing around SO, I've found a solution. Example 1 in this answer was especially helpful.

我需要做的是从主线程中调用PyEval_InitThreads()(从非常神秘的文档).然后,要使PyGILState_Ensure()能够从其他线程中完全获取GIL(或者陷入Python源代码中的无限循环中,不断尝试并无法获取GIL),我需要在主线程中释放GIL.通过调用PyEval_SaveThread()线程.最后,我应该通过调用PyEval_RestoreThread()再次在主线程中检索GIL(确保所有要调用PyGILState_Ensure()的线程在此之前都已完成,否则,由于与以前相同的原因,再次冒着被锁的风险) ).

What I need to do is to call PyEval_InitThreads() from the main thread (not at all clear from the very cryptic documentation). Then, to enable PyGILState_Ensure() to acquire the GIL at all from other threads (or else get stuck in an infinite loop within the Python source code, continually trying and failing to acquire the GIL), I need to release the GIL in the main thread via a call to PyEval_SaveThread(). Finally, I should retrieve the GIL in the main thread again via a call to PyEval_RestoreThread() (making sure that all threads that want to call PyGILState_Ensure() are definitely finished before that, or else risking a lock again for the same reason as before).

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

Here is an updated version of main.cpp which solves the problem:

#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();
}

这篇关于QThread尝试通过PyGILState_Ensure()获取Python GIL时出现死锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆