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 function

主要属性:
 
1、activeThreadCount: 此属性表示线程池中的活动线程数,通过activeThreadCount() 调用。
2、expiryTimeout: 线程活着的时间。没有设置expiryTimeout毫秒的线程会自动退出,此类线程将根据需要重新启动。默认的expiryTimeout为30000毫秒 (30 秒)。如果expiryTimeout为负, 则新创建的线程将不会过期, 在线程池被销毁之前, 它们将不会退出。通过expiryTimeout()调用,通setExpiryTimeout(int expiryTimeout)设置 。
3、maxThreadCount : int 表示线程池使用的最大线程数。
通过maxThreadCount() 调用,通过setMaxThreadCount(int maxThreadCount) 设置
注意:即使maxThreadCount限制为零或为负数, 线程池也至少有1个线程。
 
主要成员函数
 
QThreadPool *QThreadPool::globalInstance()
返回Qt应用程序全局线程池实例。
void reserveThread()
预约一个线程,这个函数总是会增加活动线程的数量。这意味着通过使用这个函数,activeThreadCount()可以返回一个大于maxThreadCount()的值。
void releaseThread()
释放以前通过调用reserveThread()预约的线程。
如果不先预约一个线程,调用这个函数会临时增加maxThreadCount()。当线程进入休眠等待时,能够允许其他线程继续。
要记得在完成等待时调用reserveThread(),以便线程池可以正确控制activeThreadCount()。
void QThreadPool :: start(QRunnable * runnable,int priority = 0)
 
在任务数量小于maxThreadCount时,为每个runnable任务预约一个线程。超过maxThreadCount时,将任务放入运行队列中。priority 参数用来设置线程运行优先级。
 
bool tryStart(QRunnable *runnable)
此方法尝试预约一个线程来运行runnable。
如果在调用的时候没有线程可用,那么这个函数什么都不做,并返回false。否则,将使用一个可用线程立即运行runnable,并返回此函数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()). The 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 own 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()
用于删除在任务队列中,还没有启动的任务。
bool tryTake(QRunnable *runnable)
如果runnable任务还没开始运行,那么从队列中删除此runable任务,此时函数返回true;如果runnable任务已经运行,返回false。
只用来删除runnable->autoDelete() == false的runnable任务,否则可能会删错任务.

  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 this 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.
  Note that this flag must be set (it is already set to true by the default constructor) before calling QThreadPool::start(). Calling this function after QThreadPool::start() will have unpredictable results.

bool waitForDone(int msecs = -1)
等待msecs毫秒, 以便所有线程退出并从线程池中移除所有线程。如果删除了所有线程, 则返回true ,否则, 它将返回false。默认等待时间为-1,即等待最后一个线程退出。
 
内容出自: 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 for representing 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:

  • Inherit QRunnable. Same as 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, the results executed in the thread can be directly sent to the main program through signals, while the QRunnable thread cannot use signal slots , only through other methods.
  • The way to start the thread is different. QThread threads can be started directly by calling the start() function, while QRunnable threads need to be started with QThreadPool.
  • Resource management is different. The QThread thread object needs to be manually managed to delete and release , while the QRunnable will be automatically released after the QThreadPool call is completed .

2.1.3 Summary

  • As a rare base class among Qt classes, QRunnable provides concise and effective creation of runnable objects . It is very suitable to use QRunnable to create independent runnable objects to run data processing processes that do not involve interface elements .
  • Advantages: The creation process is simple, easy to use, and with its own autoDelete feature, it feels a bit like "come when you call it, and go away when you wave it".
  • Disadvantages: It 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, which 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 try to use as few as possible in the project Use multiple inheritance.
  • Use QMetaObject::invokeMethod .

2.3.1 Introduction to QMetaObject::invokeMethod

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.

//函数定义
[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(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument())

QMetaObject::invokeMethod can be called asynchronously or synchronously. It depends on its connection method Qt::ConnectionType type. If the type is Qt::DirectConnection, it is a synchronous call, if it is Qt::QueuedConnection, it is an asynchronous call.

Let's see, in the above example, how we let the QRunnable thread communicate with the external main thread through QMetaObject::invokeMethod.

If we define a function in the main interface [ this function is a member function in the QObject type ] to update the content of the interface:

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;//主界面需要刷新对象,即setText()对应的类对象
};


// .cpp
CusRunnable::CusRunnable(QObject * obj):
    m_pObj(obj)
{}
 
CusRunnable::~CusRunnable()
{
    qDebug() << __FUNCTION__;
}
 
void CusRunnable::run()
{
    qDebug() << __FUNCTION__ << QThread::currentThreadId();
    
  //其中"setText"就是要调用的函数,
  //传参方式Q_ARG(QString,"this is AA!"),表示传入一个QString类型,值为"this is AA!"
  QMetaObject::invokeMethod(m_pObj,"setText",Q_ARG(QString,"this is AA!"));
    QThread::msleep(1000);
}

The article is transferred from the blog garden (fighting against the Buddha Monkey King): QThreadPool and QRunnable use of Qt multi-threaded programming

Guess you like

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