Qt5多线程/线程池技术集锦

681 阅读5分钟

1、用QObject的方法实现多线程

Qt有两种多线程的方法,其中一种是继承QThread的run函数,另外一种是把一个继承于QObject的类转移到一个Thread里。 Qt4.8之前都是使用继承QThread的run这种方法,但是Qt4.8之后,Qt官方建议使用第二种方法。

第二种方法用QObject来实现多线程有个非常好的优点,就是默认就支持事件循环(Qt的许多非GUI类也需要事件循环支持,如QTimerQTcpSocket),QThread要支持事件循环需要在QThread::run()中调用QThread::exec()来提供对消息循环的支持,否则那些需要事件循环支持的类都不能正常发送信号,因此如果要使用信号和槽,那就直接使用QObject来实现多线程。

doc.qt.io/qt-5/thread…

wiki.qt.io/QThreads_ge…

wiki.qt.io/Threads_Eve…

 

2、用QtConcurrent::run的方法实现多线程

doc.qt.io/qt-5/qtconc…

www.coologic.cn/2017/12/608…

2.1 基本的使用方式,注意线程id的获取方式是QThread::currentThreadId();

QString id = QString::asprintf("[%d]", QThread::currentThreadId());

#include <QCoreApplication>
#include <QDebug>
#include <QList>
#include <QThread>
#include <QtConcurrent>
void function(const QList<int> &param1, const int &param2, Qt::HANDLE main_id)
{
    qDebug() << "function param:" << param1 << param2 << main_id;
    qDebug() << "function thread id:" << QThread::currentThreadId();
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QList<int> testVactor;
    for (int i = 1; i <= 3; i++)
    {
        testVactor.push_back(i);
    }
    qDebug() << "main thread id:" << QThread::currentThreadId();
    QFuture<void> f = QtConcurrent::run(function, testVactor, 666, QThread::currentThreadId());
    f.waitForFinished(); //要等待,否则线程没运行完程序结束会出错
    return 0;
}

2.2 指定线程池的使用方式

有时候希望运行的函数在全局线程池或者局部线程池运行,而不是有qt托管处理,可以进行如下方式调用:

extern void aFunction();
QThreadPool pool;
QFuture<void> future = QtConcurrent::run(&pool, aFunction);

QtConcurrent::run和线程池组合使用的源码案例:

#include <QCoreApplication>
// Qt includes
#include <QtConcurrent>
#include <QtCore>
#include <QtGui>
#include <QtNetwork>
#if (QT_VERSION > QT_VERSION_CHECK(5, 0, 0))
#include <QtWidgets>
#endif

QThreadPool m_threadpool;

void func(void)
{
    qDebug() << "hello";

    QFuture<void> f4 = QtConcurrent::run(&m_threadpool, [&](void) {
        for (int i = 1000; i < 1010; i++)
        {
            qDebug() << i;
            Sleep(1000);
        }
    });
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    //初始化线程池
    int cpu = std::thread::hardware_concurrency();
    qDebug() << "cpu:" << cpu;
    m_threadpool.setMaxThreadCount(cpu);
    m_threadpool.setExpiryTimeout(-1);

    QFuture<void> f1 = QtConcurrent::run(&m_threadpool, [&](void) {
        for (int i = 0; i < 10; i++)
        {
            qDebug() << i;
            Sleep(1000);
        }
    });

    QFuture<void> f2 = QtConcurrent::run(&m_threadpool, [&](void) {
        for (int i = 100; i < 110; i++)
        {
            qDebug() << i;
            Sleep(1000);
        }
    });

    QFuture<void> f3 = QtConcurrent::run(func);

    while (true)
    {
        ;
    }

    return a.exec();
}

2.3 使用QFutureWatcher实现进度条的例子

wiki.qt.io/Progress_Ba…

\Qt5.12.9\Examples\Qt-5.12.9\qtconcurrent\progressdialog

长时间进行操作时,通常需要显示进度条。 在某些耗时的运算中,我们没有方法来实时跟踪操作的进度,所知的只是运算完成时。 为了实现进度条,您可以使用progressBar小部件,然后在另一个线程中运行该操作(使用moveToThread)。 这通常需要创建一个特殊的对象(QObject的子类,该对象运行操作,然后发出finish信号),如果您需要对许多不同的操作执行此操作,则可能会很麻烦。但是,使用QFutureWatcher和QtConcurrent::run(),这非常容易。 这篇官方文档演示如何将这种技术与QProgressDialog和QProgressBar一起使用。

如果QProgressBar最小值和最大值都设置为0,进度条会显示一个繁忙指示,而不会显示当前的值。

 

3、线程池

3.1 全局线程池
QThreadPool提供了一个静态成员函数,QThreadPool *  globalInstance(),使用此方法可获取一个当前进程的全局线程池,可在多个类中共同使用一个线程池。

3.2 局部线程池
和常规类的使用相同,可以通过QThreadPool pool;类的实例化方式建立一个局部线程池,并由当前类维护,可保证此线程池仅供当前类应用。

3.3 QThreadPool启动的线程,需要从QRunnable类继承。需要实现QRunnable类的run虚函数。QRunnable的autoDelete默认返回true,若需要更改需要调用setAutoDelete进行更改。QRunnable只有run、autodelete、setautodelete这三个关键函数。

3.4 主要的几个函数

  • int activeThreadCount() const //当前的活动线程数量
  • void clear()//清除所有当前排队但未开始运行的任务
  • int expiryTimeout() const//线程长时间未使用将会自动退出节约资源,此函数返回等待时间。默认值30000ms。
  • int maxThreadCount() const//线程池可维护的最大线程数量。默认值CPU核心数*2。
  • void setExpiryTimeout(int expiryTimeout)//设置线程回收的等待时间;可以设置为-1,没有超时限制。
  • void setMaxThreadCount(int maxThreadCount)//设置最大线程数量
  • void start(QRunnable *runnable, int priority = 0)//加入一个运算到队列,注意start不一定立刻启动,只是插入到队列,排到了才会开始运行。需要传入QRunnable,priority是线程优先级。 
  • bool waitForDone(int msecs = -1)//等待所有线程运行结束并退出,参数为等待时间,-1表示一直等待到最后一个线程退出

doc.qt.io/qt-5/qthrea…

doc.qt.io/qt-5/qrunna…

www.cnblogs.com/techiel/p/8…

3.5 线程池基本用法

#include <QCoreApplication>
#include <QDebug>
#include <QRunnable>
#include <QThread>
#include <QThreadPool>

class MyRun : public QRunnable
{
public:
    MyRun()
    {
    }

    ~MyRun()
    {
    }

public:
    void run()
    {
        int i = 3;
        while (i)
        {
            i--;
            qDebug() << "thread start:" << QThread::currentThreadId();
            QThread::msleep(500);
        }
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug() << "Main:" << QThread::currentThreadId();
    QThreadPool m;
    m.setMaxThreadCount(2);
    m.setExpiryTimeout(-1);
    MyRun *run = new MyRun;
    if (!run->autoDelete())
    {
        qDebug() << "QRunnable's autoDelete default value is not true";
        run->setAutoDelete(true);
    }

    qDebug() << m.maxThreadCount() << m.expiryTimeout();
    qDebug() << m.activeThreadCount();
    m.start(run);
    qDebug() << m.activeThreadCount();
    m.waitForDone();
    qDebug() << m.activeThreadCount();
    return 0;
}

3.6 线程池内的子线程发信号

需要派生QObject!

class MyThread_draw2D_SectionChart : public QObject, public QRunnable
{
	Q_OBJECT

public:
	MyThread_draw2D_SectionChart(int frameNo) :
		m_iFrameNo(frameNo)
	{
	}

	virtual ~MyThread_draw2D_SectionChart()
	{
		qDebug() << "~MyThread";
	}

public:
	virtual void run();

private:
	int m_iFrameNo;

signals:
	void sig_updateUI_SectionChart(int frameNo);
};

 

4、子线程发信号给主线程,更新UI

通过信号槽的机制可以实现,子线程发信号给主线程即可。不会阻塞。

启动线程
m_future_2d_SectionChart = QtConcurrent::run(this, &FormContent::thread_draw2D_SectionChart);

子线程
void FormContent::thread_draw2D_SectionChart(void)//绘制2D截面图
{
	qDebug() << "sub thread id:" << QThread::currentThreadId();
	connect(this, SIGNAL(sig_updateUI_SectionChart()), this, SLOT(slot_updateUI_SectionChart()));

while (true)
{
//去主线程更新UI
		emit sig_updateUI_SectionChart();
}

}

主线程
void FormContent::slot_updateUI_SectionChart(void)//更新2D截面图
{
   qDebug() << "main thread id:" << QThread::currentThreadId();
	m_pFormTimeline->updateSectionChart();
}

 

5、数据保护,加锁解锁

QMutex 提供相互排斥的锁,或互斥量;有递归和非递归之分;
QMutexLocker 是一个辅助类,自动对 QMutex 加锁与解锁;
QReadWriterLock 提供了一个可以同时读操作的锁;有递归和非递归之分;
QReadLocker与QWriteLocker 自动对QReadWriteLock 加锁与解锁;
QSemaphore 提供了一个整型信号量,是互斥量的泛化;
QWaitCondition 提供了一种方法,使得线程可以在被另外线程唤醒之前一直休眠。

doc.qt.io/qt-5/qmutex…

doc.qt.io/qt-5/qmutex…

doc.qt.io/qt-5/qreadl…

doc.qt.io/qt-5/qwrite…

doc.qt.io/qt-5/qreadw…

doc.qt.io/qt-5/qsemap…

blog.csdn.net/guoyufeng25…

 

6、线程优先级

Qthread的优先级属性:Priority指示系统如何调度线程。

QThread::IdlePriority 0

scheduled only when no other threads are running.

QThread::LowestPriority 1

scheduled less often than LowPriority.

QThread::LowPriority 2

scheduled less often than NormalPriority.

QThread::NormalPriority 3

the default priority of the operating system.

QThread::HighPriority 4

scheduled more often than NormalPriority.

QThread::HighestPriority 5

scheduled more often than HighPriority.

QThread::TimeCriticalPriority 6

scheduled as often as possible.

QThread::InheritPriority 7

use the same priority as the creating thread. This is the default.

 

x、多线程的高级应用

TCP Client/TCP Server

sourceforge.net/projects/th…

sourceforge.net/p/threader-…

future/promise【推荐】

github.com/mhogomchung…

y、参考文献

github.com/czyt1988/cz…

www.cnblogs.com/xia-weiwen/…

github.com/Xia-Weiwen/…