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
Some additional comments:
- 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();
toreturn 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.