QtConcurred 生成的线程与主线程具有相同的 ID

QtConcurred spawned threads have same ID as main thread

在我的桌面软件中,我从主 window 启动了几个 QtConcurrent 线程。我用 QThread::currentThreadId() 检查了线程 ID,发现它们与 GUI 线程具有相同的 ID。做了一些实验,发现罪魁祸首是 waitForFinished() 方法。但是以一种很奇怪的方式...

我编写了一个最小测试,在单击按钮时生成 qtconcurrent。 运行 方法更新旋转框上的计数器。

#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QDebug>
#include <QThread>
#include <qtconcurrentrun.h>

MainWindow::MainWindow(QWidget *parent)
  : QMainWindow(parent)
  , ui_(new Ui::MainWindow)
  , cnt_(0)
{
  ui_->setupUi(this);
  connect(this, &MainWindow::setSpinBoxValue, ui_->spinBox, &QSpinBox::setValue);

  qDebug() << "MainWindow() thread ID: " << QThread::currentThreadId();
}

MainWindow::~MainWindow()
{
  qDebug() << "~MainWindow() thread ID: " << QThread::currentThreadId();

  delete ui_;
}

void MainWindow::on_pushButton_clicked()
{
  qDebug() << "on_pushButton_clicked() thread ID: " << QThread::currentThreadId();

  QFuture<void> th = QtConcurrent::run(this, &MainWindow::updateValue);

  // TEST MADE ENABLING/DISABLING THE FOLLOWING LINE
  th.waitForFinished();
}

void MainWindow::updateValue()
{
  qDebug() << "updateValue() thread ID: " << QThread::currentThreadId();

  emit setSpinBoxValue(++cnt_);
}

如果我 remove/comment th.waitForFinished() 行调试输出显示点击线程和并发线程的不同 ID,正如预期的那样:

14:38:17: Debugging starts
MainWindow() thread ID:  0x1ad0
on_pushButton_clicked() thread ID:  0x1ad0
updateValue() thread ID:  0xc40
on_pushButton_clicked() thread ID:  0x1ad0
updateValue() thread ID:  0xc40
on_pushButton_clicked() thread ID:  0x1ad0
updateValue() thread ID:  0xc40
~MainWindow() thread ID:  0x1ad0
14:38:27: Debugging has finished

如果我等待线程终止(th.waitForFinished() 调用),第一次并发 运行s 在新线程中,然后它 运行s 在主 GUI 线程上所有连续调用

14:40:23: Debugging starts
MainWindow() thread ID:  0x764
on_pushButton_clicked() thread ID:  0x764
updateValue() thread ID:  0x83c
on_pushButton_clicked() thread ID:  0x764
updateValue() thread ID:  0x764
on_pushButton_clicked() thread ID:  0x764
updateValue() thread ID:  0x764
~MainWindow() thread ID:  0x764
14:40:35: Debugging has finished

为什么会这样?

我想这与等待是在主线程中完成有关。但是一旦等待完成,软件就可以自由创建一个新线程,所以我不知道为什么会这样。

我正在研究 Windows 10,Qt 5.15.2 MSVC2019 x64

这似乎是一种意外行为,因为它没有记录在案。也许理解问题原因的关键是默认情况下 QtConcurrent::run() 使用 QThreadPool::globalInstance() 但如果创建了不同的 QThreadPool 则不会观察到此问题:

void MainWindow::on_pushButton_clicked()
{
  qDebug() << "on_pushButton_clicked() thread ID: " << QThread::currentThreadId();

  QThreadPool pool;
  QFuture<void> th = QtConcurrent::run(&pool, this, &MainWindow::updateValue);

  // TEST MADE ENABLING/DISABLING THE FOLLOWING LINE
  th.waitForFinished();
}

我在 Qt bugreports 上聊过这个话题。

虽然没有记录,但注意到的行为是预期的。总结:

  • QtConcurrent::run()被称为
  • waiForFinished()被称为
  • 主线程需要停止等待其他线程完成
  • 但是当行为与执行函数调用完全一样时,为什么还要费心创建一个新线程呢? => Qt在一个函数调用中自动转换QtConcurrent::run()(在主线程中执行)

如果创建多个线程就不会发生这种情况

QFuture<void> th[2];
for(unsigned int i = 0; i < 2; i++)
  th[i] = QtConcurrent::run(this, &MainWindow::updateValue, i);
for(unsigned int i = 0; i < 2; i++)
  th[i].waitForFinished();

在这种情况下,Qt 不能在函数调用中转换QtConcurrent::run(),否则2 个线程不会运行 并行。因此将创建 2 个新线程(两个 updateValue() 函数将在 ID 与主线程 ID 不同的线程中 运行)。

要解决单线程问题,请使用 QFutureWatcher 而不是 waitForFinished(),它会在线程终止时发出信号;主线程不会被锁定(当插槽连接到信号时,主线程将 return 锁定),每次 QtConcurrent::run() 调用都会创建新线程。