我的Qt程序中集成CPython,多线程执行结果异常

问题描述 投票:0回答:1
// Automatically fetched and automatically released when out of scope
class PyGILState
{
public:
    PyGILState()
        : gstate(PyGILState_Ensure())
    {
    }

    ~PyGILState()
    {
        PyGILState_Release(gstate);
    }

    PyGILState(const PyGILState&) = delete;
    PyGILState& operator=(const PyGILState&) = delete;

    PyGILState_STATE gstate;
};

// The class that manages Cpython is moved to a child thread
class PythonThread : public QObject {
    Q_OBJECT
public:
    PythonThread(QObject *parent = nullptr) : QObject(parent) {}

    ~PythonThread() { }

    PyObject* module;

    void newPythonEnv(){
        qDebug() << "newPythonEnv thread id : " << QThread::currentThreadId();

        Py_SetPythonHome(L"D://python");  // Set your Python installation path
        Py_SetPath(L"D://python//Lib;D://python//DLLs;D://python;");

        Py_Initialize();
        PyEval_InitThreads();  // Initialize thread support

        PyRun_SimpleString("import sys");
        PyRun_SimpleString("sys.path.append('./script')");

        module = PyImport_ImportModule("test1");  //Import Python script modules
    }

public slots:
    // start state machine
    void start() {
        qDebug() << "State machine start...";
        PyGILState pyGILState;

        PyObject* pFunc = PyObject_GetAttrString(module, "state_machine");

        if (pFunc && PyCallable_Check(pFunc)) {
            PyObject_CallObject(pFunc, nullptr);
        } else {
            PyErr_Print();
            std::cerr << "Failed to call state_machine function" << std::endl;
        }
        qDebug() << "State machine thread end...";
    }

    // stop state machine
    void stop() {
        qDebug() << "State machine stop...";
        PyGILState pyGILState;

        PyObject *globals_dict = PyModule_GetDict(module);
        if (globals_dict) {
            PyDict_SetItemString(globals_dict, "isRuning", Py_False);
            qDebug() << PyLong_AsLong(PyDict_GetItemString(globals_dict, "isRuning"));

        } else {
            qDebug() << "Failed to get globals_dict";
        }
        qDebug() << "State machine stopped";
    }
}

//MainWindow
class MainWindow : public QWidget {
    Q_OBJECT
public:
    MainWindow() {
        pythonThread = new PythonThread;

        thread = new QThread;
        pythonThread->moveToThread(thread);
        thread->start();

        connect(this, &MainWindow::start_thread, pythonThread, &PythonThread::start);
        connect(this, &MainWindow::new_python_env, pythonThread, &PythonThread::newPythonEnv);

        qDebug() << "Main thread id: " << QThread::currentThreadId();
        emit new_python_env();

        QVBoxLayout* layout = new QVBoxLayout(this);
        QPushButton* startButton = new QPushButton("Start State Machine");
        QPushButton* stopButton = new QPushButton("Stop State Machine");

        layout->addWidget(startButton);
        layout->addWidget(stopButton);


        connect(startButton, &QPushButton::clicked, this, &MainWindow::startStateMachine);
        connect(stopButton, &QPushButton::clicked, this, &MainWindow::stopStateMachine);

        setLayout(layout);
        setWindowTitle("Python State Machine Controller");
    }

signals:
    void start_thread();
    void new_python_env();

public slots:
    void startStateMachine() {
        emit start_thread();
    }

    void stopStateMachine() {
        pythonThread->stop();
    }


private:
    PythonThread* pythonThread;
    QThread *thread;
};

Python 脚本代码

isRuning = True 

def state_machine():
    global isRuning
    print('exec ... state machine ',flush=True)
    isRuning = True
    isPause = False
    while isRuning:
        print(f"state machine runing ...  {isRuning}",flush=True)
        time.sleep(0.1)
        while isPause == True : 
            print('wait continue ...', flush=True)
            # time.sleep(1)
            if isRuning == False : 
                return
    isRuning = True

这是可以重现问题的最小实现

关于控制台输出:

流程1:点击启动状态机按钮,几秒后点击停止状态机按钮

日志如下:

Main thread id:  0x399c
newPythonEnv thread id :  0x658
State machine start...
exec ... state machine 
state machine runing ...  True
state machine runing ...  True
state machine runing ...  True
state machine runing ...  True
state machine runing ...  True
State machine stop...
0
State machine stopped
State machine thread end...

流程2:软件启动后点击停止状态机按钮即可

日志如下:

Main thread id:  0x4b20
newPythonEnv thread id :  0x1070
State machine stop...

(之后UI将无法工作)

在 UI 无法正常工作的图像下方: it's getting stuck

我尝试过的:

我尝试在主线程上创建一个python解析器,但仍然不起作用。

预期结果:

我希望“启动状态机”按钮启动 python 状态机,“停止状态机”按钮结束 python 状态机。是的,在我的最小实现中,如果我按顺序单击按钮,它就会起作用。但如果我只是单击“停止状态机”按钮,它就会卡住。

c++ qt cpython
1个回答
0
投票

您在尝试获取 GIL 时显然陷入了困境。

创建Python环境后,您需要在返回之前通过调用PyEval_SaveThread来释放GIL,否则GIL将永远不会被释放。

当worker运行时,它会从该线程释放GIL,如果worker从未运行过,那么GIL将永远不会被释放,并且主线程试图获取它是一个死锁。

© www.soinside.com 2019 - 2024. All rights reserved.