2022-06-19 Qt 多线程

文章目录

  • 前言
  • 一.Qt线程模型
  • 二.Qt线程 与C++线程关系
  • 三.线程安全与可重入性
      • 1.线程安全类
      • 2.可重入类
  • 四.线程具体使用
      • 1.QThread run
      • 2. QThread moveToThread
      • 3. QThreadPool
      • 4.QtConcurrent
      • 5.选择合适的线程
      • 6.示例
      • 7.Qt中线程间通讯的方式
      • 8.线程同步
      • 9.条件锁
  • 五.示例


前言

Qt中线程的学习记录
Qt线程方式:

  1. QThread :低级别多线程,手支管理线程生命周期(创建,启动,线束)
  2. QRunnable: 高级另多线程,由线程池负责回收线程,空返回值,即发即弃
  3. QtConcurrent:高级别线程,由线程池负责加收线束,可以有返回值,可以同时对线程进行监测和操作

一.Qt线程模型

  • Qt使用依赖于平台的线程,QThread只是一个平台线程的管理器
  • 默认情况下,Qt的每个进程都有一个线程,GUI应用程序的主线程即为GUI线程
  • 线程共享一个堆内存,但也有自己的栈内存
  • QThread有一个特定于线程的事件循环

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。

二.Qt线程 与C++线程关系

  • 都有类似的功能集

    • 都有原子操作: 不会被线程的调用而打断
    • 同步锁: 保证每个线程 都能执行原子操作
    • 互斥锁: 避免多个线程抢占资源
    • 都有条件变量: 使用程序可以在睡眠中等待条件的到达
    • future模式:接收线程的结果,异步的.c++11之后才有.
  • Qt提供线程池

  • 基本经验法则

    • 如果线程中需要使用到信号槽等特性,使用QThread
    • 如果线程中不需要使用任何QObject的特性,可以使用C++线程

三.线程安全与可重入性

  • 在多线程环境中,线程安全的函数可以实现数据共享,需不需要单独考虑数据同步

  • 可重入函数支持多个线程同时结其进行调用,需无需考虑外部数据同步
    2022-06-19 Qt 多线程_第1张图片

  • 所有线程安全的函数都是可重入的,但反之不一定成立.

1.线程安全类

  • 如果一个类里所有成员函数都是线程安全的,那这个类就是线程安全类
  • 这种类实例可以被多个线程去共享操作
  • Qt 中线程安全类很少: 线程安全类会性能损失
  • 有些函数是线程安全的,如:
    • QObject::connect()
    • QCoreApplication::postEvent()
    • signal

2.可重入类

  • 可重入类的所有成员函数都是可重入的
  • 可重入类可以大多线程中使用,但每个线程都会有自己的类实例
  • Qt库的大部分类都是可重入的
  • 包含大多隐式共享的值类型,但不包括QPixmap
  • 大部分的QObject子类,所UI相关的除外 如QWidgetQQuickItems
  • QSvgGenerator QSvgRenderer
  • 富文本处理类 QTextDocument,甚至包括它的clone()函数

四.线程具体使用

1.QThread run

继承QTread 和movetothread这两种线程的方式,太常见了,网络上资料也多的很,这里就不详细描述,看官方文档或者网络上一查即可:
Qt 文档中案例:

 class WorkerThread : public QThread
 {
     Q_OBJECT
     void run() override {
         QString result;
         /* ... here is the expensive or blocking operation ... */
         emit resultReady(result);
     }
 signals:
     void resultReady(const QString &s);
 };

 void MyObject::startWorkInAThread()
 {
     WorkerThread *workerThread = new WorkerThread(this);
     connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
     connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
     workerThread->start();
 }

2. QThread moveToThread

Qt 文档中案例:

class Worker : public QObject
 {
     Q_OBJECT

 public slots:
     void doWork(const QString &parameter) {
         QString result;
         /* ... here is the expensive or blocking operation ... */
         emit resultReady(result);
     }

 signals:
     void resultReady(const QString &result);
 };

 class Controller : public QObject
 {
     Q_OBJECT
     QThread workerThread;
 public:
     Controller() {
         Worker *worker = new Worker;
         worker->moveToThread(&workerThread);
         connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
         connect(this, &Controller::operate, worker, &Worker::doWork);
         connect(worker, &Worker::resultReady, this, &Controller::handleResults);
         workerThread.start();
     }
     ~Controller() {
         workerThread.quit();
         workerThread.wait();
     }
 public slots:
     void handleResults(const QString &);
 signals:
     void operate(const QString &);
 };

3. QThreadPool

使用方式也很简单,类似QTthread的run

 class HelloWorldTask : public QRunnable
 {
     void run() override
     {
         qDebug() << "Hello world from thread" << QThread::currentThread();
     }
 };

 HelloWorldTask *hello = new HelloWorldTask();
 // QThreadPool takes ownership and deletes 'hello' automatically
 QThreadPool::globalInstance()->start(hello);

4.QtConcurrent

Qt 的高级线程,使用起来更简单,有点你std::thread

int myFunc0()
{
	while(1){...}
}
int myFunc1(int param1,const QString& param2)
{
	while(1){...}
}
class MyClass{
public:
	int myFunc2(int param1,const QString& param2)
	{
		while(1){...}
	}
}
void test()
{
	/// 普通函数用法,不带参
	QFuture<int> result = QtConcurrent::run(myFunc1);
	
	int p1 = 10;QString  p2 = "hello";
	/// 普通函数用法,带参
	QFuture<int> result = QtConcurrent::run(myFunc1,p1,p2);

	MyClass cl;
	/// 类成员函数
	QFuture<int> result = QtConcurrent::run(&cl,&MyClass::myFunc2,p1,p2);
	
	//lambda表达式
	QFuture<int> result = QtConcurrent::run([=](){
		while(1){...}
	});
}


QFuture 是一个异步计算的结果
+ 横版类,没见有继承QObject
+ 使用pause()和resume()函数支持暂停和恢复功能(仅当任务仍在队列中时)
+ 提供了进度信息
+ 其它有用的函数(isFinished, isRunning, isStarted, waitForFinished)
+ 利用线程池中的空闲线程

利用QFutureWatcher 可以实现线程的监听

5.选择合适的线程

各方案比较

特征 QThread QRunnable and QThreadPool QtConcurrent::run() Qt Concurrent (Map, Filter, Reduce) WorkerScript
语言 c++ c++ c++ c++ qml
可以指定线程优先级
线程内可以事件循环
线程内可以通过信号接收数据更新
线程可以通过信号控制
线程可以通过QFuture监听
内置暂停/恢复/取消功能

6.示例

QThread生命周期 操作 方案
一次调用 从线程内发送线程更新状态 Qt提供了不同的解决方案:
1. QThread::run()的重新实现并启动,通过发信号以更新进度
2. 重新实现QRunnable::run(),使用QThreadPool进行启动,写入线程安全变量以更新进度.
3. 使用QtConcurrent::Run()运行该函数,写入线程安全变量以更新进度
一次调用 从线程内发送线程更新状态并获取返回值 使用 QtConcurrent: : Run ()运行该函数。让 QFutureWatcher 在函数返回时发出完成()信号,并调用 QFutureWatcher: : result ()来获取函数的返回值。
一次调用 使用所有可用的核心对容器的所有项执行操作。例如,从图像列表生成缩略图。 使用 Qt Concurrent 的 QtConcurrent: : filter ()函数选择容器元素,使用 QtConcurrent: : map ()函数对每个元素应用操作。要将输出折叠成单个结果,可以使用 QtConcurrent: : filteredReduced()和 QtConcurrent: : mappedReduced()代替。
一次调用/持久的 在纯 QML 应用程序中进行长时间的计算,并在结果准备就绪时更新 GUI 将计算代码放在一个脚本中,并将其附加到一个 WorkScript 实例。调用 WorkerScript.sendMessage ()在新线程中启动计算。让脚本也调用 sendMessage () ,将结果传递回 GUI 线程。在那里处理结果并更新 GUI
持久的 让一个对象生活在另一个线程中,该线程可以根据请求执行不同的任务和/或接收要处理的新数据 子类化一个 QObject 来创建一个 worker。实例化这个 worker 对象和一个 QThread。将工作线程移动到新线程。通过排队的信号槽连接向辅助对象发送命令或数据
持久的 在另一个线程中重复执行代价高昂的操作,其中该线程不需要接收任何信号或事件 直接在 QThread: : run ()的重新实现中编写无限循环。不使用事件循环启动线程。让线程发出信号将数据发送回 GUI 线程

https://doc.qt.io/qt-5/threads-technologies.html
Qt官方有提到,我这里只是翻译了一下

7.Qt中线程间通讯的方式

  • 直接调用函数,此方式一定要保护线程安全,保护不同线程间会使用到的变量
  • 信号槽,推荐使用,但线程中必须要有一个正在运行的事件循环
  • 使用QMetaObject,可以从线程内发出
  • 使用QCoreApplication::postEvent() 发送事件
  • QWaitCondition 加上互斥锁保护的线程全局数据

8.线程同步

  • QMutex : 保护对共享资源的访问,用法同std::mutex
    + lock(),unlock()
    + tryLock()
    + tryLock(int timeout)
    + 为了简化可以使用QMutexLocket 如: QMutexLocker locker(&m_mutex);它的生命周期结束即释放锁,同 std::lock_guard
  • QReadWriteLock: 与QMutex相比,增加了并发性,允许多次读取
  • QWaitCondition:
    + 几个线程可能会同时待待一个条件
    + 可以随机唤醒一个或所有线程
    + 通常一个线程负责等待,一个线程负责唤醒
  • QSemaphore,QSystemSemaphore: 保护多个相同的资源
    + QSemaphoreReleaser 可以释放信号量,如:const QSemaphoreReleaser releaser(semaphore);

9.条件锁

  • QWaitCondition::wait() 让线程等待某个事件
  • 可以指定最大的等待时间
  • 必须使用QMutex(而不是QReadWriteLock),才能从锁定状态进入等待状态
  • 互斥对象将在线程被唤醒之前自动锁定
  • 使用QWaitCondition::wakeOne()随机唤醒一个等待条件的线程,或者使用 QWaitCondition::wakeAll() 唤醒所有等待的线程

五.示例

提供了代码示例,有需要的可以去看看
https://github.com/tianxiaofan/QtMultiThread

你可能感兴趣的:(QT,日常记录,qt,开发语言)