我不知道 OP 已经问了一个非常相似的问题
SO: Problem with using WINAPI threads in Qt in C++
(在撰写本文时)已经有两个有价值的答案。
尽管如此,我还是发布了我在注意到上述内容之前完成的示例。
多线程通常需要额外注意线程间通信。与多处理相反(默认情况下,每个进程都有未共享的变量,并且需要对Inter-Process Communication 进行特殊处理),所有线程都可以访问相同的进程变量。只要每个线程在其生命周期内专门使用其变量,一切都很好。一旦至少一个线程读取了一个被另一个线程修改的变量,麻烦就开始了。为了防止data races and race-conditions,需要人工作者负责的线程同步。忽略这一点(即使是无意的)会引入Undefined Behavior,在这种情况下,编译器不会为此提供任何有用的诊断。
Qt GUI 不适用于多线程。因此,小部件和小部件属性不是线程安全的。
除此之外,Qt 还为多线程做好了准备。
-
QObject 准备具有线程亲和性。 →Thread Affinity
-
Qt 信号可用于线程间通信。为此,QObject::connect() 的所有相关风格都提供了Qt::ConnectionType 的参数。当一个 Qt 信号连接到另一个QObject 的插槽时,默认情况下,发送者和接收者对象的线程亲和性将用于适当调整连接类型:
如果接收者位于发出信号的线程中,则使用 Qt::DirectConnection。否则,使用 Qt::QueuedConnection。连接类型在信号发出时确定。
-
如果非 GUI 线程调用 GUI 对象的成员函数,这肯定会导致未定义行为(由于缺乏线程安全性)。相反,线程可以向 GUI 线程的事件循环添加请求,以便 GUI 线程可以按顺序处理并同步。为此,可以使用QApplication::postEvent()。显然,这是罕见的 Qt GUI 函数之一,它被明确标记为 thread-safe。
Thread Support in Qt 中给出了更全面的概述
- 另一个选择是使用
std:: C++ 多线程工具,如std::thread 来生成和加入线程,std::mutex(和公司)保护共享访问,和/或std::atomic 用于(可能) 无锁的线程间通信。
这样做的好处是可以使用(可能已经存在的)线程安全代码,它专门基于std:: C++ 库的东西。 Qt GUI 的(定期)更新由QTimer 管理,这是一种轮询,但会导致简单的刷新率管理,而不会有其他线程开始淹没 GUI 线程的事件循环的危险。 (过于频繁地更新 Qt GUI 可能会导致相当大的性能影响,并显着降低其反应性。)
关于选项 4,我曾经为另一个类似的问题写了一个答案 SO: Qt C++ Displaying images outside the GUI thread (Boost thread)。
回想起来,我为 OP 的问题写了一个新的示例:
C++源码testQProgressMultiThreading.cc:
// standard C++ header:
#include <atomic>
#include <chrono>
#include <thread>
// Qt header:
#include <QtWidgets>
// a wrapper for a thread with some added context
struct Worker {
const uint id; // constant over thread runtime - no sync. needed
std::thread thread; // the thread instance
std::atomic<uint> progress; // shared data (written in worker, read by UI)
std::atomic<bool> exit; // flag to signal abort (from UI to worker)
Worker(uint id): id(id), progress(0), exit(true) { }
~Worker() { if (thread.joinable()) thread.join(); }
void start()
{
if (thread.joinable()) return; // worker already working
qDebug() << "Start worker " << id;
progress = 0; exit = false;
thread = std::thread(&Worker::work, this);
}
void stop()
{
if (!thread.joinable()) return; // worker not working
exit = true;
thread.join();
progress = 0;
qDebug() << "Worker" << id << "finished.";
}
void work()
{
qDebug() << "Enter worker " << id;
while (progress < 100 && !exit) {
// consume some time (without heating the CPU too much)
std::this_thread::sleep_for(std::chrono::milliseconds(50));
// confirm some work progress
++progress;
}
qDebug() << "Leaving worker " << id;
}
bool working() const { return thread.joinable(); }
};
// main application
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// workers
Worker worker1(1), worker2(2), worker3(3);
// setup GUI
QWidget qWinMain;
qWinMain.setWindowTitle("Test QProgress Multi-Threading");
qWinMain.resize(640, 480);
QVBoxLayout qVBox;
QHBoxLayout qHBox;
qHBox.addStretch();
QPushButton qBtnStart1("Start 1");
qHBox.addWidget(&qBtnStart1);
QPushButton qBtnStart2("Start 2");
qHBox.addWidget(&qBtnStart2);
QPushButton qBtnStart3("Start 3");
qHBox.addWidget(&qBtnStart3);
QPushButton qBtnStop("Stop");
qHBox.addWidget(&qBtnStop);
qVBox.addLayout(&qHBox);
QProgressBar qProgress1;
qVBox.addWidget(&qProgress1);
QProgressBar qProgress2;
qVBox.addWidget(&qProgress2);
QProgressBar qProgress3;
qVBox.addWidget(&qProgress3);
qWinMain.setLayout(&qVBox);
qWinMain.show();
// prepare timer
QTimer qTimerProgress;
qTimerProgress.setInterval(50); // update rate for GUI 50 ms -> 20 Hz (round about)
// install signal-handlers
QObject::connect(&qBtnStart1, &QPushButton::clicked,
[&](bool) {
worker1.start();
if (!qTimerProgress.isActive()) qTimerProgress.start();
});
QObject::connect(&qBtnStart2, &QPushButton::clicked,
[&](bool) {
worker2.start();
if (!qTimerProgress.isActive()) qTimerProgress.start();
});
QObject::connect(&qBtnStart3, &QPushButton::clicked,
[&](bool) {
worker3.start();
if (!qTimerProgress.isActive()) qTimerProgress.start();
});
QObject::connect(&qBtnStop, &QPushButton::clicked,
[&](bool) {
worker1.stop(); qProgress1.setValue(worker1.progress);
worker2.stop(); qProgress2.setValue(worker2.progress);
worker3.stop(); qProgress3.setValue(worker3.progress);
qTimerProgress.stop();
});
QObject::connect(&qTimerProgress, &QTimer::timeout,
[&](){
qProgress1.setValue(worker1.progress);
if (worker1.progress >= 100) worker1.stop();
qProgress2.setValue(worker2.progress);
if (worker2.progress >= 100) worker2.stop();
qProgress3.setValue(worker3.progress);
if (worker3.progress >= 100) worker3.stop();
if (!worker1.working() && !worker2.working() && !worker3.working()) {
qTimerProgress.stop();
}
});
// runtime loop
return app.exec();
}
一个CMake build-script CMakeLists.txt 准备一个VS解决方案:
project(QProgressMultiThreading)
cmake_minimum_required(VERSION 3.10.0)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(Qt5Widgets CONFIG REQUIRED)
include_directories("${CMAKE_SOURCE_DIR}")
add_executable(testQProgressMultiThreading testQProgressMultiThreading.cc)
target_link_libraries(testQProgressMultiThreading Qt5::Widgets)
在 VS2017(Windows 10、Qt 5.13)中构建和测试: