问题
I need to alter three progress bars simultaneously in three WINAPI threads. Since my funtions, that change value of progress bars are to have access to these bars, they are members of MainWindow class. So i created wrappers with signature that is required by CreateThread
function, but I still get segmentation fault when I try to start my threads. What it can be caused by and how to handle this and get my code to work?
Example of my function:
DWORD MainWindow::ThreadFunc1()
{
for(int i = 0; i < 100; i++)
{
ui->progressBar->setValue(i);
sleep(500);
}
return 0;
}
Example of my wrapper
DWORD WINAPI MainWindow::threadStart1(LPVOID lpParams)
{
MainWindow* This = (MainWindow*) lpParams;
return This->ThreadFunc1();
}
回答1:
I was not aware that OP already asked a very similar question
SO: Problem with using WINAPI threads in Qt in C++
which has (at the time of this writing) already two valuable answers.
Nevertheless, I publish my sample which I had complete before noticing the above.
Multi-threading usually requires additional care for the interthread communication. In opposition to multi-processing (where each process has unshared variables by default and requires special effort for Inter-Process Communication), all threads may access the same process variables. As long as each thread uses its variables exclusively during its life-time, everything is fine. As soon as at least one thread reads a variable which is modified by another thread, the trouble starts. To prevent data races and race-conditions, a thread synchronization is needed for which the human author is responsible. Ignoring this (even unintendedly) introduces Undefined Behavior, and, in such cases, the compiler doesn't contribute any helpful diagnosis for this.
The Qt GUI is not intended for multi-threading. Hence, the widgets and widget properties are not thread-safe.
Beside of this, Qt is prepared for multi-threading.
QObject
is prepared to have a thread affinity. → Thread AffinityQt signals can be used for interthread communication. For this, all relevant flavors of QObject::connect() provide a parameter of Qt::ConnectionType. When a Qt signal is connected to a slot of another
QObject
, by default, the thread affinities of sender and receiver object will be used to adjust the connection type appropriately:If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used. The connection type is determined when the signal is emitted.
If a non-GUI thread calls member functions of GUI objects, this results surely in Undefined Behavior (due to the lack of thread-safety). Instead, the thread may add a request to the event loop of the GUI thread so that the GUI thread can process it in order and sync. For this, QApplication::postEvent() can be used. Apparently, this is one of the rare Qt GUI functions which is explicitly remarked as thread-safe.
A more encompassing overview is given in Thread Support in Qt
- Another option is the usage of the
std::
C++ multi-threading tools likestd::thread
to spawn and join threads,std::mutex
(and company) to guard shared accesses, and/orstd::atomic
for a (potentially) lock-free interthread communication.
This has the advantage that (mayby already existing) thread-safe code can be used which is based on std::
C++ library stuff exclusively. The (periodical) update of Qt GUI is managed by a QTimer
which is some kind of polling but results in a simple refresh-rate management without the danger that other threads start to flood the event loop of the GUI thread. (Updating the Qt GUI too frequently may result in a considerable performance impact with a noticable drop of its reactivity.)
About option 4, I once wrote an answer SO: Qt C++ Displaying images outside the GUI thread (Boost thread) for yet another similar question.
Recalling this, I wrote a new sample for the question of OP:
The C++ source 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();
}
A CMake build-script CMakeLists.txt
to prepare a VS solution:
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)
Built and tested in VS2017 (Windows 10, Qt 5.13):
来源:https://stackoverflow.com/questions/61744306/how-to-alter-qt-widgets-in-winapi-threads