QThread of Qt multithreaded programming

Background introduction [GUI main thread + sub-thread]

  Much like C++11, Qt uses QThread to manage threads, and a QThread object manages a thread. There are many similarities in use to C++11, but more are unique to Qt. Content. In addition, the QThread object also has a message loop exec() function, that is, each thread has a message loop to process events of its own thread.

  QCoreApplication::exec() is always called in the main thread (the thread that executes main()). In a GUI program, the main thread is also called the GUI thread, which is the only thread that allows GUI-related operations. All the Other thread tasks must be attached to the main thread. Therefore, to create a QThread thread task, the premise is that a QApplication (or QCoreApplication) object must be created.

  When developing a GUI application, it is assumed that the application needs to process more complex logic in some cases . If there is only one thread to process it, it will cause the window to freeze and cannot process the relevant operations of the user. In this case, it is necessary to use multithreading. One thread handles window events, and other threads perform logic operations. Multiple threads perform their duties, which can not only improve the user experience but also improve the execution efficiency of the program.

  Multi-threading is used in qt, some things need extra attention:

  • The default thread is called window thread in Qt, also called main thread or GUI thread , responsible for window event processing or window control data update
  • The sub-thread is responsible for the business logic processing in the background. The sub-thread cannot perform any operations on the window object. These things need to be handed over to the window thread for processing.
  • If you want to transfer data between the main thread and the child thread, you need to use the signal slot mechanism in Qt

1. QThread thread class

Qt provides a QThread thread class [ inherited from QObject, different from QRunnable ], through which sub-threads can be created.

The benefits of this article, free to receive Qt development learning materials package, technical video, including (C++ language foundation, introduction to Qt programming, QT signal and slot mechanism, QT interface development-image drawing, QT network, QT database programming, QT project combat, QSS, OpenCV, Quick module, interview questions, etc.) ↓↓↓↓↓↓See below↓↓Click on the bottom of the article to receive the fee↓↓ 

1.1 Commonly used member functions

// 构造函数,父类QObject 
QThread::QThread(QObject *parent = Q_NULLPTR);
// 判断线程中的任务是不是处理完毕了
bool QThread::isFinished() const;
// 判断子线程是不是在执行任务
bool QThread::isRunning() const;

// 获取、设置线程的优先级
Priority QThread::priority() const;
void QThread::setPriority(Priority priority);
优先级:
    QThread::IdlePriority         --> 最低的优先级
    QThread::LowestPriority
    QThread::LowPriority
    QThread::NormalPriority
    QThread::HighPriority
    QThread::HighestPriority
    QThread::TimeCriticalPriority --> 最高的优先级
    QThread::InheritPriority      --> 子线程和其父线程的优先级相同, 默认是这个
// 退出线程, 停止底层的事件循环
// 退出线程的工作函数
void QThread::exit(int returnCode = 0);
// 调用线程退出函数之后, 线程不会马上退出因为当前任务有可能还没有完成, 调回用这个函数是
// 等待任务完成, 然后退出线程, 一般情况下会在 exit() 后边调用这个函数
bool QThread::wait(unsigned long time = ULONG_MAX);

1.2 Signal slots

//等同于exit() 效果,之后也要调 wait() 函数
[slot] void QThread::quit();
// 启动子线程
[slot] void QThread::start(Priority priority = InheritPriority);

// 函数用于强制结束线程,不保证数据完整性和资源释放,慎用
[slot] void QThread::terminate();

// 线程中执行完任务后, 发出该信号
[signal] void QThread::finished();
// 开始工作之前发出这个信号, 一般不使用
[signal] void QThread::started();

1.3 Static functions

// 当前执行线程的QThread指针对象
[static] QThread *QThread::currentThread();
// 返回系统上运行的理想线程数 == 和当前电脑的 CPU 核心数相同
[static] int QThread::idealThreadCount();
// 线程休眠函数
[static] void QThread::msleep(unsigned long msecs);    // 单位: 毫秒
[static] void QThread::sleep(unsigned long secs);    // 单位: 秒
[static] void QThread::usleep(unsigned long usecs);    // 单位: 微秒

1.4 Task processing function

// 子线程要处理什么任务, 需要写到 run() 中
[virtual protected] void QThread::run();
//线程的起点,在调用start()之后,新创建的线程就会调用run函数,默认实现调用exec(),run函数返回时,线程的执行将结束。

Two, two methods of QThread

2.1 The method of deriving the QThread class object (rewriting the Run function)

2.1.1 Use steps:

  1. Create a subclass MyThread that inherits from the QThread thread class, that is, derive QThread;
  2. Rewrite the thread task function run () method in the MyThread class, and write the specific business process and thread entry to be processed by the sub-thread in this function;
  3. Create a MyThread sub-thread object in the main thread, and call the start () method to start the MyThread sub-thread;

2.1.2 Precautions:

  1. The run () method cannot be called outside the class to start the child thread. Calling start () outside the class is equivalent to letting run () start running
  2. In Qt, do not operate the window type object in the program in the child thread, it is not allowed, if the program is operated, it will hang
  3. Only the main thread can operate the window object in the program. The default thread is the main thread, and the one created by itself is the child thread.

2.1.3 Examples:

Try to use multi-threading to achieve 10s time-consuming operation: (trigger with button)

Thread class workThread:

//workThread .h
class workThread : public QThread
{
public:
    void run();
};

//workThread.cpp
workThread::workThread(QObject* parent)
{}
//线程入口:主要处理的后台业务逻辑或数据更新等
void workThread::run()
{
    qDebug() << "当前子线程ID:" << QThread::currentThreadId();
    qDebug() << "开始执行线程";
    QThread::sleep(10);
    qDebug() << "线程结束";
}

Enable sub-threads in the main thread of the window:

//Threadtest .h
class Threadtest : public QMainWindow
{
    Q_OBJECT

public:
    Threadtest(QWidget *parent = Q_NULLPTR);

private:
    Ui::ThreadtestClass ui;
    void btn_clicked();
    workThread* thread;
};

//threadtest.cpp
Threadtest::Threadtest(QWidget* parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);
    connect(ui.btn_start, &QPushButton::clicked, this, &Threadtest::btn_clicked);
    thread = new workThread ; //主线程中创建workThread子线程对象,
} 

void Threadtest::btn_clicked() 
{ 
  qDebug() << "主线程id:" << QThread::currentThreadId(); 
  thread->start();//启动子线程
}

2.2.moveToThread+ slot function link binding thread interface

Use the method of QThread derived class object to create a thread. This method has a limitation. Assuming that multiple tasks are to be processed in a sub-thread, all processing logic needs to be written in the run() function, so that the processing in this function The logic will become very confusing and not easy to maintain . Therefore, the second thread creation method provided by Qt makes up for the shortcomings of the first method, and it is more flexible to use, that is, the method of using signals and slots, that is, the function executed in the thread (we can call it a thread function) ) is defined as a slot function.

2.2.1 Steps to use

  • Create a new class (mywork), let this class derive from QObject, add a public member function (working) to this class, and the function body is the business logic we want to execute in the child thread
  • Create a QThread object in the main thread , which is the object of the child thread
  • Create a working class object in the main thread (never specify a parent object for the created object)
  • To move the MyWork object to the created child thread object, you need to call the moveToThread() method provided by the QObject class
  • Start the child thread, call start(), at this time the thread starts, but the object moved to the thread does not work
  • Call the work function of the MyWork class object to let the function start to execute. At this time, it runs in the sub-thread to which it is moved

2.2.2 Code sample

//MyWork .h
class MyWork : public QObject
{
    Q_OBJECT
public:
    explicit MyWork(QObject *parent = nullptr);
    // 工作函数
    void working();
signals:
    void curNumber(int num);
public slots:
};

//mywork.cpp
MyWork::MyWork(QObject *parent) : QObject(parent)
{}
void MyWork::working()
{
    qDebug() << "当前线程对象的地址: " << QThread::currentThread();
    int num = 0;
    while(1)
    {
        emit curNumber(num++);
        if(num == 10000000)
            break;
        QThread::usleep(1);
    }
    qDebug() << "run() 执行完毕, 子线程退出...";
}


//主程序
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    qDebug() << "主线程对象的地址: " << QThread::currentThread();
    // 创建线程对象
    QThread* sub = new QThread ;
    // 创建工作的类对象
    // 千万不要指定给创建的对象指定父对象
    // 如果指定了: QObject::moveToThread: Cannot move objects with a parent
    MyWork* work = new MyWork;
    // 将工作的类对象移动到创建的子线程对象中
    work->moveToThread(sub);
    // 启动线程
    sub->start();
    // 让工作的对象开始工作, 点击开始按钮, 开始工作
    connect(ui->startBtn, &QPushButton::clicked, work, &MyWork::working);//signal对象为主线程,slot函数为子线程,分处不同的线程中
    // 显示数据
    connect(work, &MyWork::curNumber, this, [=](int num)
    {
        ui->label->setNum(num);
    });
}

MainWindow::~MainWindow()
{
    delete ui;
}

2.2.3 Precautions

Using this multi-threading method, assuming that there are multiple unrelated business processes that need to be processed, then you can create multiple classes similar to MyWork, put the business processes in the public member functions of multiple classes, and then put the business processes of this business class Just move the instance object to the corresponding sub-thread with moveToThread(), which can make the written program more flexible, more readable, and easier to maintain. When using it, pay attention to:

  • Do not operate UI in sub-threads: In sub-threads created by Qt, no operations can be performed on UI objects, that is, QWidget and its derived class objects. The Qt neutron thread cannot perform any processing on the interface. The correct operation should be to pass some parameters to the main thread through the signal slot, and let the main thread (that is, the Controller) handle it.
  • When declaring and initializing the task class, do not specify the parent object: For example, in the above program: MyWork* work = new MyWork;
  • Cross-thread signal slot: When using the connect function in the relationship between QThread and connect, we generally ignore the last parameter, which is the fifth parameter. The last parameter represents the connection method: connect(ui->startBtn, &QPushButton::clicked, work, &MyWork::working);//The signal object is the main thread, and the slot function is a sub-thread, which is divided into different threads
  1. Automatic connection (AutoConnection): the default connection method. If the signal and slot, that is, the sender and receiver are in the same thread, it is equivalent to a direct connection; if the sender and receiver are in different threads, it is equivalent to a queue connection.
  2. Direct Connection (DirectConnection) : When the signal is emitted, the slot function will be called directly. Regardless of which thread the object of the slot function is in, the slot function is executed on the thread where the emitter is located.
  3. Queue connection (QueuedConnection) : When control returns to the event loop of the thread where the receiver is located, the slot function is called. The slot function is executed in the same thread as the receiver.
  4. Blocking Queued Connection (BlockingQueuedConnection): The signal and the slot must be in different threads, otherwise a deadlock will occur. The calling situation of the slot function is the same as that of the Queued Connection, the difference is that the current thread will be blocked until the slot function returns [after emit Block execution until the slot execution is completed, then execute the follow-up code of emit]
  5. Unique Connection (UniqueConnection): It is used in conjunction with the first four. Make sure that same signal, same slot remains uniquely connected. The function is to make the same signal uniquely connect to the same slot. But when you connect next time, if you don't use Qt::UniqueConnection, the next connection will still succeed, and the unique connection will not take effect. It takes effect to use Qt::UniqueConnection twice.

3. Thread safety issues in Qt

QThread inherits from QObject, emits signals to indicate the start and end of thread execution, and provides many slot functions. QObjects can be used with multiple threads, emitting signals to call slots in other threads, and sending events to objects that "live" in other threads .

3.1 Thread Synchronization

3.1.1 Basic concept of thread synchronization

  • Critical resources: resources that are only allowed to be accessed by one thread at a time
  • Inter-thread mutual exclusion: multiple threads need to access critical resources at the same time

Thread locks can ensure the security of critical resources. Usually, each critical resource requires a thread lock for protection .

Thread deadlock: Threads wait for each other for critical resources so that each other cannot continue to execute.

Conditions for deadlock:

  • There are multiple critical resources in the system and the critical resources cannot be preempted
  • A thread requires more than a critical resource to continue executing

QMutex, QReadWriteLock, QSemaphore, QWaitCondition provide means of thread synchronization. The main idea of ​​using threads is to hope that they can be executed as concurrently as possible, and some key points need to stop or wait between threads

3.1.2 Mutex QMutex, QMutexLocker, QWaitCondition

  • QMutex provides a mutually exclusive lock, or mutex. At most one thread owns a mutex at a time. If a thread tries to access a mutex that has been locked, the thread will sleep until the thread that owns the mutex unlocks the mutex. QMutex is often used to protect shared data access. If Mutex.lock ( ) without the corresponding use of Mutex.unlcok() will cause deadlock, and other threads will never get the opportunity to access the shared resources locked by Mutex; QMutexLocker is similar to std::mutex in c++
  • In more complex functions and exception handling, it will be very complicated to perform lock() and unlock() operations on mutex objects of the QMutex class , and the overhead is also high, so Qt introduces the auxiliary class QMutexLocker of QMutex to avoid lock() and unlock () operation . Create a QMutexLocker object where the function needs it, and pass the mutex pointer to the QMutexLocker object. At this time, the mutex has been locked. After exiting the function, the local variable of the QMutexLocker object will be destroyed by itself, and the mutex will be unlocked at this time. QMutexLocker is similar to std::lock_guard<> in c++
  • QWaitCondition allows threads to wake up other threads when certain conditions occur. One or more threads can block waiting for QWaitCondition, set a condition with wakeOne() or wakeAll(). wakeOne() wakes up one at random, and wakeAll() wakes up all. QWaitCondition is similar to condition_variable in c++

3.2 Reentrancy of QObject [Using multi-threading technology in QTcpsocket]

A thread-safe function can be called by multiple threads at the same time, and there is no problem even if the caller uses shared data , because access to shared data is serial. A reentrant function can also be called by multiple threads at the same time, but each caller can only use its own data. Therefore, a thread-safe function is always reentrant, but a reentrant function is not necessarily thread-safe.

A reentrant class means that the member functions of the class can be safely called by multiple threads, as long as each thread uses a different object of the class. And a thread-safe class means that the member functions of the class can be safely called by multiple threads, even if all threads use the same instance of the class.

QObject is reentrant, and most non-GUI subclasses of QObject such as QTimer, QTcpSocket, QUdpSocket, QHttp, QFtp, QProcess are also reentrant, and it is possible to use these classes in multiple threads at the same time . Reentrant classes are designed to be created and used in a single thread, creating an object in one thread and calling the object's functions in another thread is not guaranteed to work. There are three constraints to be aware of:

  1. A QObject instance must be created in the same thread as its parent class. This means that, in general, never use the QThread object (this) as the parent of an object created in the thread (because the QThread object itself is created in another thread, that is, QThread* t = new QThread, do not (this ) ).
  2. Event-driven objects may only be used in a single thread. Especially useful for timer mechanisms and networking modules. For example: you cannot start a timer or connect a socket in a thread that does not belong to this object, you must ensure that all objects created in this thread are deleted before deleting QThread (objects created in thread need to be released before the thread is released). . This can be easily done by creating these objects on the stack in the implementation of the run() function.
  3. Although QObject is reentrant, GUI classes, especially QWidget and all its subclasses, are not reentrant and can only be used in the GUI thread. QCoreApplication::exec() must also be called from the GUI thread .

In practice, classes that can only use the GUI in the main thread and not in other threads can be easily solved: put the time-consuming operation in a separate worker thread, and when the worker thread is finished, it will be displayed by the screen in the GUI thread. Show results. In general, creating a QObject before a QApplication is not acceptable and will cause weird crashes or exits, depending on the platform. Therefore, static instances of QObject are not supported. A single-threaded or multi-threaded application should create the QApplication first, and destroy the QObject last .

3.3 Thread event loop

  • Each thread has its own event loop . The main thread starts its own event loop through QCoreApplication::exec(), but the GUI application of the dialog box sometimes uses QDialog::exec(), and other threads can use QThread::exec() to start the event loop. Just like QCoreApplication, QThread provides an exit(int) function and quit() slot function, which should be used together with wait().
  • The signal slot mechanism allows the emission (emission thread) to be connected to the receiving thread : the event loop allows the thread to use some non-GUI Qt classes that require the existence of an event loop (for example: QTimer, QTcpSocket, and QProcess), making it possible to connect some threads It is possible to signal to a thread-specific slot function.
  • A QObject instance is said to live on the thread in which it was created. Events about this object are dispatched to the thread's event loop. You can use the QObject::thread() method to get the thread in which a QObject is located. The QObject::moveToThread() function changes the threading attribute of an object and its children. (Objects cannot be moved to other threads if they have a parent).
  • It is not safe to call the delete method on this QObject object from another thread (not the thread to which the QObject object belongs) , unless it is guaranteed that the object does not process events at that time. It is better to use QObejct::deleteLater(). An event of type DeferredDelete will be posted, and the event loop of the object's thread will eventually handle the event. By default, the thread that owns a QObject is the thread that created the QObject, not the one after QObject::moveToThread() was called.
  • If no event loop is running, events will not be delivered to the object. For example: a QTimer object is created in a thread, but exec() is never called, then QTimer will never emit the timeout() signal, even if deleteLater() is called. (These restrictions also apply to the main thread).
  • Using the thread-safe method QCoreApplication::postEvent(), events can be sent to any object in any thread at any time, and the event will be automatically distributed to the thread event loop where the object was created.
  • All threads support event filters, with the restriction that the monitor object must exist in the same thread as the monitored object. QCoreApplication::sendEvent() (unlike postEvent()) can only dispatch events to objects in the same thread as the function caller.

In engineering practice, in order to avoid freezing the event loop of the main thread (that is, to avoid freezing the UI of the application), all computing work is done in a separate worker thread, and a signal is emitted when the worker thread ends. The parameter sends the state of the worker thread to the slot function of the GUI thread to update the state of the GUI component .

The article is transferred from the blog garden (fighting against the Buddha Monkey King): QThread of Qt multi-threaded programming - Fighting against Buddha's Monkey King- Blog Garden

The benefits of this article, free to receive Qt development learning materials package, technical video, including (C++ language foundation, introduction to Qt programming, QT signal and slot mechanism, QT interface development-image drawing, QT network, QT database programming, QT project combat, QSS, OpenCV, Quick module, interview questions, etc.) ↓↓↓↓↓↓See below↓↓Click on the bottom of the article to receive the fee↓↓ 

Guess you like

Origin blog.csdn.net/QtCompany/article/details/132239046