总结:QT 多线程(处理密集时的界面响应保持)

目录

前因:

起因:如果处理一个特定任务上耗费的时间过多时,那么用户界面就会变得无法响应。

问题:怎么保持在程序密集响应时,界面不会卡住?

在此种情况下的解决方案:

一、利用processEvents()函数

二、使用多线程(QT开启多线程的三种方式)

1、继承QThread重写虚函数run()

2、moveToThread

3、QtConCurrent::run()并发

 三、QThread线程安全释放顺序

四、同步线程

☋·QMutex

☋·QReadWriteLock

五、在次线程中使用qt类(QObject是可重入的,但必须记住他有三个约束条件)

☋·QObject的子对象必须在它的父对象线程中创建

☋·在删除对应的QThread对象之前,必须删除所有在次线程中创建的QObject对象

☋·必须在创建QObject对象的线程中删除它们


前因:

当调用QApplication::exec()时,就启动了QT的事件循环。在开始的时候QT会发出一些事件命令来显示和绘制窗口部件。

在这之后,事件循环就开始运行,它不断检查是否有事件发生并且把这些事件发生给应用程序的QObject。

当处理一个事件时,也可能同时产生一些其他的事件并且将其追加到QT的事件队列中。如果在处理一个特定事件上耗费的事件过多,那么用户界面将变得无法响应。例如,在应用程序把一个文件保存到磁盘的过程中,直到文件保存完毕,才会处理那些由窗口系统产生的事件;在文件保存的过程中,应用程序就不能响应来自窗口系统重新绘制的请求。

起因:如果处理一个特定任务上耗费的时间过多时,那么用户界面就会变得无法响应。

问题:怎么保持在程序密集响应时,界面不会卡住?

在此种情况下的解决方案:

一、利用processEvents()函数

在代码中频繁调用该函数即可QApplication::processEvents()。这个函数告诉QT处理所有那些还没有被处理的各类事件,然后将控制权返回给调用者。 实际上,QApplication::processEvents()就是一个不停调用processEvent()函数的while循环。

示例:

void test::writeFile(const QString &sFileName)
{
    QFile f(sFileName);
    ……
    for(int i=0;i<size;i++)
    {
        代码
        qApp->processEvent();
    }
}

使用这个方法的时候存在一个潜在的问题:应用程序还在执行的时候,就关闭了主窗口或者点击了其他响应,会产生预料不到的后果。解决方案:替换成QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); 以告诉QT忽略鼠标事件和键盘事件。

 补充:QMetaObject::invokeMethod()使用解决界面卡住问题

二、使用多线程(QT开启多线程的三种方式)

一个线程用于处理应用程序中的用户界面,另一个线程则执行文件保存操作(或任意其他耗时的操作),这样的话,在保存文件的时候,应用程序的用户界面仍可以保持响应。

1、继承QThread重写虚函数run()

示例: 

☋.h

☋ .cpp里放耗时的操作

调用:开启线程,调用start之后就会执行run函数了。

	SendMsgThread *smt = new SendMsgThread();
    smt->start();

2、moveToThread

原型:void QObject::moveToThread ( QThread * targetThread )子类化QObject

如何利用moveToThread开启多线程:
第一步:创建一个继承QObject的子类,在这里起的类名假定为MyObject,在类里定义一个槽函数doWorker()

第二步:使用moveToThread的方法创建线程:

·☋ 实例化MyObject

    MyObject *myobject1 = new MyObject();

·☋ 实例化QThread

	QThread  *thread1 = new QThread();

·☋ 将创建出来的MyObject加入到QThread中

	myobject1->moveToThread(thread1);

·☋ 将信号与槽关联起来,自带的线程信号有两个,一个是started,一个是finished,顾名思义,分别是当线程启动和结束的时候.这里只展示started的,可以使用自己自定义的信号

	QObject::connect(thread1,SIGNAL(started()),
myobject1,SLOT(doWorker()),Qt::QueuedConnection);

·☋ 最后一步线程开启后,就会在线程里面执行函数里面的内容

    thread1->start();

注:同一个QObject的对象只能对应一个线程

 

3、QtConCurrent::run()并发

QtConcurrent这是一个高级 API,构建于QThreadPool之上,它提供更高层次的函数接口(APIs),使所写的程序,可根据计算机的CPU核数,自动调整运行的线程数量.主要功能是令启动一个线程来执行一个函数.
注意,QtConcurrent是一个命名空间而不是一个类,因此其中的所有函数都是命名空间内的全局函数。使用时pro文件要添加:QT += concurrent
头文件:

使用方法:QtConcurrent::run()可以开辟一个单独的线程来运行我们定义的函数(可以在里面执行一些耗时或者需要并行的计算),QtConcurrent::run()返回一个QFuture模板类,用来处理函数的返回值。

相关函数说明:QFuture future;//可以获得计算的结果值

future.waitForFinished();等待线程结束,实现阻塞
future. isFinished() 判断线程是否结束
future.isRunning() 判断线程是否在运行
future.result()取出线程函数的返回值

示例:根据线程执行的函数可分为以下三种
1 全局函数或静态函作为线程函数并且线程执行不带参数
QFuture QtConcurrent::run(Function function, …),例:

2 线程执行类成员函数(带参数):run的第一个参数必须是const引用或者对象指针

 

3 结构体函数作为线程函数

sturct worker
{
    int ID;
    void worker::threadFunc()
    { }
};

int main()
{
    worker work;
    QtConcurrent::run(&work,&worker::threadFunc); 
}

 三、QThread线程安全释放顺序

     QThread *thread = new QThread();
     thread->quit();
     thread->deleteLater();
     thread = NULL;//避免成为野指针
     thread->wait();

quit()——停止线程的循环事件,如果线程没有事件循环则什么都不做;

wait()——会阻塞到线程执行完,才执行wait后面的代码,线程退出,wait会返回;

deleteLater()——删除某个对象,防止内存泄露;同一个对象调用多次deleteLater()不会照成多重删除;但并不是立即执行deleteLater(),而是

>当Object回到事件循环中,对象将会被删除;

>线程中如果没有事件循环,那么当线程完成后就会被删除
 

还有terminate()函数也可以终止线程,但是这种方法并不安全,因为它可以随时停止线程而不给这个线程自我情况的机会。

四、同步线程

对于多线程应用程序,一个最基本要求就是能实现几个线程的同步执行,QT提供了一下几个用于同步的类:QMutex、QReadWriteLock、QSemaphore和QWaitCondition

☋·QMutex

提供了一种保护一个变量或一段代码的方法,这样就可以每次只让一个线程读取它。

原理:这个类提供了一个lock()函数来锁住互斥量mutex,如果互斥量是解锁的unlock,那么当前线程就立即占用并锁定lock它;否则,当前线程就会被阻塞,直到掌握这个互斥量的线程对它解锁为止。QMutex类还提供一个tryLock()函数,如果互斥量已经锁住,它就会立即返回。

示例:

QMutex mutex;

void test()
{
    mutex.lock();
    代码……
    mutex.unlock();
}

使用互斥量存在一个问题:每次只能有一个线程可以访问同一变量。在程序中可能会有多线程同时尝试访问读取同一变量(不修改),此时互斥量可能就会成为一个严重的性能瓶颈。在这种情况下,可以使用QReadWriteLock,他是一个同步类,允许同时执行多个读取访问而不会影响性能。

☋·QReadWriteLock

a.读写锁的特性:读共享,写独占。
读共享
当其他线程占用读锁的时候,如果其他线程请求读锁会立即获得
当其他线程占用读锁的时候,如果其他线程请求写锁,会阻塞等待读锁的释放
写独占
当其他线程占用写锁的时候,如果其他线程请求读锁,会阻塞等待写锁的释放
当其他线程占用写锁的时候,如果其他线程请求写锁,会阻塞等待写锁的释放。

b.读写优先级
默认优先级是写优先,即写锁的优先级>读锁,哪怕是读先排队的也没用。

3、常用函数包含:

lockForRead() ;请求读锁
lockForWrite() ;请求写锁
tryLockForRead() ;尝试请求读锁,非阻塞函数,可以设置超时时间
tryLockForWrite() ;尝试请求写锁,非阻塞函数,可以设置超时时间
unlock() ;解锁(解读锁和解写锁,均使用该函数)

示例:

Mydata data;
QReadWriteLock lock;

void ReadThread::run()
{
    ……
    lock.lockForRead();
    读data
    ……
    lock.unlock();
}

void WriteThread::run()
{
    ……
    lock.lockForWrite();
    ……
    写data
    lock.unlock();
}

为简便起见,我们可以使用QreadLocker类和QWriteLocker类对QReadWriteLock进行锁定和解锁。

五、在次线程中使用qt类(QObject是可重入的,但必须记住他有三个约束条件)

当函数可以同时被不同的线程安全地调用时,就称其为线程安全的。

可重入:如果类的不同实例可同时用于不同的线程,那么这个类就是重入的。

然而在多个线程中同时访问同一个可重入对象是不安全的,而是应该用一个互斥量来保护这个类的访问。一个类是否可重入,在qt参考文档有标记。通常情况下,任何没有被全局引用或者被其他数据引用的c++类都认为是可重入的。

QObject是可重入的,但是必须记住他有三个约束条件:

☋·QObject的子对象必须在它的父对象线程中创建

特别需要说明的是,这一约束条件意味着,在次线程中创建的对象永远不能将QThread对象作为创建他们的父对象,因为QThread对象是在另一个线程(主线程或另外一个不同的次线程中创建的)。非常重要,不然会报错,你的父对象是另一个的子对象,大体就是这个意思

☋·在删除对应的QThread对象之前,必须删除所有在次线程中创建的QObject对象

通过在QThread::run()中的堆栈上创建这些对象,就可以完成这一点。

☋·必须在创建QObject对象的线程中删除它们

如果需要删除一个存在于不同线程中的QObject对象,必须调用线程安全的QObject::deleteLater函数,它可以置入一个延期删除的事件。

  • 16
    点赞
  • 95
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Qt中,多线程处理大量数据可以通过使用过滤器、映射器和简化器的概念来实现。这种方法通过依次处理数据并创建少量的辅助线程来提高处理效率,从而减少系统开销。另外,Qt还提供了多种方式来分配并执行处理数据的线程。 一种方式是利用QtConcurrent::run()函数,在Qt全局线程池的辅助线程中运行函数来处理数据。这种方法可以将处理函数放入一个线程中执行,让Qt自动管理线程的创建和销毁。 另一种方式是创建QRunnable对象并在Qt全局线程池辅助线程中处理数据。通过创建QRunnable对象,可以将要处理的数据分配给不同的线程,并由Qt管理线程的执行。 还有一种方式是创建QThread对象并将其作为辅助线程来处理数据。这种方式需要手动管理线程的创建和销毁,但可以更灵活地控制线程的执行。 无论是哪种方式,多线程处理大量数据都需要注意死锁的风险,并且编写拥有多线程处理功能的程序相对于单线程来说更加困难,需要谨慎对待。 综上所述,Qt提供了多种方法和类来实现多线程处理大量数据,包括过滤器、映射器和简化器的概念,以及QtConcurrent::run()、QRunnable和QThread等类的使用。根据具体的需求,可以选择合适的方式来实现多线程处理大量数据的功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Qt高级编程之多线程处理](https://blog.csdn.net/weixin_38880029/article/details/129482609)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咸鱼2333号程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值