使用 omp 的 CPython 扩展冻结了 Qt UI

CPython extension using omp freezes Qt UI

我正在研究一种科学算法(图像处理),它是用 C++ 编写的,使用大量并行化,由 OpenMP 处理。我需要它可以从 Python 调用,所以我创建了一个 CPython 包,它处理算法的包装。 现在我需要一些 UI,因为用户交互对于初始化某些东西是必不可少的。我的问题是当我 运行 算法时 UI 冻结。我在一个单独的线程中启动算法,所以这应该不是问题(我什至通过用 time.sleep 替换函数调用来证明它,并且它工作正常,不会导致任何冻结)。为了进行测试,我将 UI 减少为两个按钮:一个用于启动算法,另一个只是将一些随机字符串打印到控制台(以检查 UI 交互)。
我也经历了一些非常奇怪的事情。如果我开始移动鼠标,然后按下按钮开始计算,然后继续移动鼠标,UI 不会冻结,所以将鼠标悬停在按钮上会给他们通常的蓝色 Windows风格的色调。但是,如果我在应用程序 window 上停止移动鼠标几秒钟,单击一个按钮,或切换到另一个 window,UI 会再次冻结。更奇怪的是,如果我将鼠标放在应用程序 window.
之外,UI 仍保持活动状态,这是我的代码(不幸的是,由于多种原因我无法共享该算法,但我希望即使这样我也设法得到了一些帮助):

if __name__ == "__main__":
    from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
    from PyQt5.QtCore import QThread, QObject, pyqtSignal
    import time
    from CustomAlgorithm import Estimator # my custom Python package implemented in C++

    class Worker(QObject):
        finished = pyqtSignal()

        def run(self):
            estimator = Estimator()
            estimator.calculate()
            # if the above two lines are commented, and the next line is uncommented,
            # everything's fine
            # time.sleep(5)
            print("done")

    app = QApplication([])
    thread = QThread()
    window = QWidget()
    layout = QVBoxLayout()
    # button to start the calculation
    btn = QPushButton("start")
    layout.addWidget(btn)
    btn.clicked.connect(thread.start)

    # button to print some text to console
    btn2 = QPushButton("other button")
    layout.addWidget(btn2)
    btn2.clicked.connect(lambda: print("other button clicked"))
    window.setLayout(layout)

    # handling events
    worker = Worker(app)
    worker.moveToThread(thread)
    thread.started.connect(worker.run)
    worker.finished.connect(thread.quit)
    worker.finished.connect(worker.deleteLater)
    thread.finished.connect(thread.deleteLater)
    window.show()
    app.exec_()

我尝试了多种使用线程的变体,例如 threading.Threadmultiprocessing.ProcessPyQt5.QtCore.QThread(如上所示),甚至是 napari 的 worker 实现,但是结果是一样的。我什至尝试从代码中删除 omp,以防它以某种方式干扰 python 线程,但它没有帮助。

至于我使用python的原因是最终目标是让我的实现在napari中可用。

非常感谢任何帮助!

由于Python的“全局解释器锁”,一次只有一个线程可以运行 Python编码。但是,其他线程可以同时做I/O。

如果你想允许其他线程 运行(就像 I/O 那样)你可以用这些宏包围你的代码:

Py_BEGIN_ALLOW_THREADS

// computation goes here

Py_END_ALLOW_THREADS

其他 Python 个线程将被允许 运行 在进行计算时。您无法从这两行之间的 Python 访问 任何内容 - 因此请按顺序 Py_BEGIN_ALLOW_THREADS 之前获取数据。

Reference