Use of QThreadPool and QRunnable for Qt multithreaded programming

When it comes to threads, you usually think of QThread, but in fact, there are many ways to create threads in Qt. Here we mainly introduce one of them, QRunnable. The usage of QRunnable and QThread is somewhat different, and the usage scenarios are also different. To introduce the usage, usage scenarios and precautions of QRunnable, we must first take a look at QThreadPool, because QRunnable tasks need to use QThreadPool to start threads.

1. QThreadPool thread pool

1.1 Introduction to thread pool

In an application, we need to use threads multiple times, which means we need to create and destroy threads multiple times. The process of creating and destroying threads is bound to consume memory. In daily development, memory resources are extremely precious, so the thread pool QThreadPool is recommended to manage the concurrent execution of multiple threads. In the program logic, it is often encountered that a large number of tasks need to be processed, such as intensive network requests, log analysis, loading multiple sub-projects in the project, saving the project, and so on. Generally, a queue will be created, and one or more threads will be used to consume the queue. Generally, the problem of locking and unlocking the queue will also be dealt with.

1.2 Advantages of thread pool

  • Creating and destroying threads requires interaction with the OS. A small number of threads has little effect, but too many threads will inevitably affect performance. Using a thread pool can reduce this overhead;
  • The thread pool maintains a certain number of threads. When using it, pass the specified function to the thread pool, and the thread pool will perform tasks in the thread

1.3 QThreadPool class member functions

Main attributes:
 
1. activeThreadCount: This attribute indicates the number of active threads in the thread pool, which is called by activeThreadCount().
2. expiryTimeout: The time the thread is alive. Threads with no expiryTimeout milliseconds set will automatically exit, and such threads will be restarted as needed. The default expiryTimeout is 30000 milliseconds (30 seconds). If expiryTimeout is negative, newly created threads will not expire and they will not exit until the thread pool is destroyed. Called by expiryTimeout(), set by setExpiryTimeout(int expiryTimeout).
3. maxThreadCount: int indicates the maximum number of threads used by the thread pool.
Called by maxThreadCount(), set by setMaxThreadCount(int maxThreadCount)
Note: Even if the maxThreadCount limit is zero or negative, the thread pool has at least 1 thread.
 
Main member function
 
QThreadPool *QThreadPool::globalInstance()
Returns the Qt application global thread pool instance.
void reserveThread()
Reserve a thread, this function will always increase the number of active threads. This means that by using this function, activeThreadCount() can return a value greater than maxThreadCount().
void releaseThread()
Releases a thread previously reserved by calling reserveThread().
Calling this function will temporarily increase maxThreadCount() if a thread is not reserved first. When a thread goes to sleep waiting, other threads can be allowed to continue.
Remember to call reserveThread() when you are done waiting so that the thread pool can properly control activeThreadCount().
void QThreadPool::start(QRunnable * runnable, int priority = 0)
 
When the number of tasks is less than maxThreadCount, reserve a thread for each runnable task. When maxThreadCount is exceeded, put the task in the run queue. The priority parameter is used to set the thread running priority.
 
bool tryStart(QRunnable *runnable)
This method attempts to reserve a thread to run the runnable.
If no threads are available at the time of the call, this function does nothing and returns false. Otherwise, the runnable is run immediately using one of the available threads, and this function returns true.
void start(QRunnable *runnable, int priority = 0)
Reserve a thread and use that thread to run the runnable, (unless the thread would cause the current thread count to exceed maxThreadCount()). runnable will be added to the run queue. The priority parameter can be used to control the execution order of the run queue.
If runnable->autoDelete() returns true, the thread pool will have ownership of the runnable, and after runnable->run() returns, the thread pool will automatically delete the runnable. If runnable->autoDelete() returns false, ownership of the runnable remains with the caller. Changing the automatic deletion of a runnable (QRunnable::setAutoDelete()) after calling this function results in undefined behavior.
void clear()
Used to delete tasks that have not been started in the task queue.
bool tryTake(QRunnable *runnable)
If the runnable task has not started running, then delete the runable task from the queue, at this time the function returns true; if the runnable task has already run, returns false.
It is only used to delete runnable tasks with runnable->autoDelete() == false, otherwise the wrong task may be deleted.
void setAutoDelete(bool autoDelete)
If autoDelete is true, automatic deletion is enabled. Otherwise automatic deletion will be disabled. If auto-deletion is enabled, QThreadPool will automatically delete the runable object after calling the run() function to return. Otherwise, the ownership of the runable object does not belong to the thread pool and is managed by the developer. Please note that this flag must be set first (the default constructor has set it to true) before calling QThreadPool::start(). Calling this function after QThreadPool::start() will have unpredictable results.
bool waitForDone(int msecs = -1)
Waits msecs milliseconds for all threads to exit and removes all threads from the thread pool. Returns true if all threads are removed, otherwise, it returns false. The default wait time is -1, which means waiting for the last thread to exit.
 
Content from: https://blog.csdn.net/y396397735/article/details/78637634

1.4 Summary

  • The QThreadPool class manages a collection of QRunnable /QThread .
  • QThreadPool manages and recycles individual QThread objects to reduce thread creation costs in programs that use threads.
  • Every Qt application has a global QThreadPool object which can be accessed by calling globalInstance().
  • To use QThreadPool, you need to subclass QRunnable and implement the run() virtual function. Then create an object of this class and pass it to QThreadPool::start(). QThreadPool automatically deletes QRunnable by default.
  • QThreadPool is a low-level class for managing threads, and the Qt Concurrent module is a more advanced solution.

2. QRunnable [Does not inherit QObject, does not belong to Qt’s meta-object system]

2.1 Introduction

2.1.1 Definition and usage

The QRunnable class is an interface used to represent a task or piece of code that needs to be executed, represented by a reimplemented run() function.
Generally use QThreadPool to execute code in a separate thread. To create a thread using QRunnable, the steps are as follows:

  • Inherits QRunnable. Just like using QThread, you first need to inherit your thread class from QRunnable.
  • Rewrite the run function. Still the same as QThread, the run function needs to be rewritten. run is a pure virtual function and must be rewritten.
  • Start threads using QThreadPool

2.1.2 The difference with QThread

  • Different ways of communicating with the outside world. Since QThread is inherited from QObject, but QRunnable is not, so in the QThread thread, you can directly send the result executed in the thread to the main program by means of a signal , and QRunnable threads cannot use signals and slots, they can only use other methods.
  • The way to start the thread is different. QThread threads can be started by directly calling the start() function, while QRunnable threads need to be started with the help of QThreadPool.
  • Resource management is different. QThread thread objects need to be manually managed to delete and release, QRunnable will be automatically released after the QThreadPool call is completed.

2.1.3 Summary

  • As a rare base class in Qt classes, QRunnable provides concise and effective creation of runnable objects. Use QRunnable to create independent runnable objects to run data processing processes that do not involve interface elements Very suitable.
  • Advantages: The creation process is simple and easy to use. With its own autoDelete feature, it feels like “it’s easy to come in and go away”.
  • Disadvantages: Cannot provide its own running status in real time.

2.2 Two ways to start threads [global thread pool and non-global thread pool]

We mentioned above that to start a QRunnable thread, QThreadPool needs to be used together, and there are two calling methods: Global thread pool and non-global thread pool.

2.2.1 Global thread pool [ QThreadPool::globalInstance() ]

Every Qt application has a global QThreadPool object, which can be accessed by calling globalInstance(). The startup method in the above example is to use the global thread pool to start QRunnable. It is very simple to use:

QThreadPool::globalInstance()->start(m_pRunnable);

2.2.2 Non-global thread pool

In addition, it can also be implemented by using a non-global thread pool. This method can control the maximum number of threads and other settings, which is more flexible. For details, refer to the help document

 QThreadPool threadpool;
 threadpool.setMaxThreadCount(1);
 threadpool.start(m_pRunnable);

2.3 External communication [ QRunnable + QMetaObject::invokeMethod ]

We mentioned earlier that because QRunnable does not inherit from QObject, it is impossible to use signal slots to communicate with the outside world, then, if you want to communicate with the outside world in the QRunnable thread, there are usually two ways:

  • Use multiple inheritance. Let our custom thread class inherit from QRunnable and QObject at the same time, so that signals and slots can be used, but multi-threading is more troublesome, especially when inheriting from a custom class, it is prone to interface confusion, so in Try to use less and more inheritance in the project.
  • UseQMetaObject::invokeMethod.

2.3.1 QMetaObject::invokeMethod introduction

This function is to try to call the member function of obj, which can be a signal, slot, or function declared by Q_INVOKABLE (which can be invoked by the Qt meta-object system). If the call is successful, it returns true, and if it fails, it returns false. The specific usage method is not introduced here.

//Function definition
[static] bool QMetaObject::invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0 = QGenericArgument( Q_NULLPTR ), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), Q GenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument gument())</ pre>
<p>QMetaObject::invokeMethod can be an asynchronous call or a synchronous call. This depends on how it is connected to the Qt::ConnectionType type. If type is Qt::DirectConnection, it is a synchronous call, if it is Qt::QueuedConnection, it is an asynchronous call.</p>
<p>Let's take a look, in the above example, how we let the QRunnable thread communicate with the external main thread through QMetaObject::invokeMethod.</p>
<p>If we define a function in the main interface [<em>This function is a member function of the QObject type</em>], it is used to update the interface content:</p>
<pre>Q_INVOKABLE void setText(QString msg);

Thread class:

// .h
class CusRunnable : public QRunnable
{
public:
    explicit CusRunnable(QObject *obj);
    ~CusRunnable();
    void run();
 
private:
    QObject * m_pObj = nullptr;//The main interface needs to refresh the object, that is, the class object corresponding to setText()
};


// .cpp
CusRunnable::CusRunnable(QObject * obj):
    m_pObj(obj)
{}
 
CusRunnable::~CusRunnable()
{
    qDebug() << __FUNCTION__;
}
 
void CusRunnable::run()
{
    qDebug() << __FUNCTION__ << QThread::currentThreadId();
    
//Where "setText" is the function to be called,
//The parameter passing method Q_ARG(QString,"this is AA!") means passing in a QString type with the value "this is AA!"
QMetaObject::invokeMethod(m_pObj,"setText",Q_ARG(QString,"this is AA!"));
    QThread::msleep(1000);
}