1、用QObject的方法实现多线程
Qt有两种多线程的方法,其中一种是继承QThread的run函数,另外一种是把一个继承于QObject的类转移到一个Thread里。 Qt4.8之前都是使用继承QThread的run这种方法,但是Qt4.8之后,Qt官方建议使用第二种方法。
第二种方法用QObject
来实现多线程有个非常好的优点,就是默认就支持事件循环(Qt的许多非GUI类也需要事件循环支持,如QTimer
、QTcpSocket
),QThread
要支持事件循环需要在QThread::run()
中调用QThread::exec()
来提供对消息循环的支持,否则那些需要事件循环支持的类都不能正常发送信号,因此如果要使用信号和槽,那就直接使用QObject
来实现多线程。
https://doc.qt.io/qt-5/threads.html
QThreads general usage - Qt Wiki
Threads Events QObjects - Qt Wiki
2、用QtConcurrent::run的方法实现多线程
https://doc.qt.io/qt-5/qtconcurrentrun.html
Qt多线程-QtConcurrent并行运算高级API-Coologic
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> ¶m1, const int ¶m2, 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;
}
如果QtConcurrent::run首个参数是this,那么本质就是开启全局的线程池
m_future_GoHome = QtConcurrent::run(this, &CProduceCore::threadfun_GoHome);
以下方法可以设置全局线程池的线程数目
//初始化全局的线程池
const int MAX_THREAD_NUM = 8;
int count = QThreadPool::globalInstance()->maxThreadCount();
if (count < MAX_THREAD_NUM)
{
QThreadPool::globalInstance()->setMaxThreadCount(MAX_THREAD_NUM);
}
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实现进度条的例子
\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表示一直等待到最后一个线程退出
https://doc.qt.io/qt-5/qthreadpool.html
https://doc.qt.io/qt-5/qrunnable.html
Qt多线程-QThreadPool线程池与QRunnable - Coologic - 博客园
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 提供了一种方法,使得线程可以在被另外线程唤醒之前一直休眠。
https://doc.qt.io/qt-5/qmutex.html
https://doc.qt.io/qt-5/qmutexlocker.html
https://doc.qt.io/qt-5/qreadlocker.html
https://doc.qt.io/qt-5/qwritelocker.html
https://doc.qt.io/qt-5/qreadwritelock.html
https://doc.qt.io/qt-5/qsemaphore.html
QT知识点总结—QMutex、QSemaphore与QReadWriteLock_guoyufeng25的博客-CSDN博客_qmutex trylock
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.
7、下一篇
Qt5多线程/线程池技术集锦(2)如何在子线程更新ui窗口_libaineu2004的博客-CSDN博客
x、多线程的高级应用
TCP Client/TCP Server
Threader download | SourceForge.net
future/promise【推荐】
https://github.com/mhogomchungu/tasks
y、参考文献
https://github.com/czyt1988/czyBlog/tree/master/tech/QtThread