`
qimo601
  • 浏览: 3454425 次
  • 性别: Icon_minigender_1
  • 来自: 苏州
社区版块
存档分类
最新评论

Qt 线程基础(QThread、QtConcurrent等)

    博客分类:
  • Qt
阅读更多

 

昨晚看Qt的Manual,突然发现下一个版本的Qt中(Qt4.7.4、Qt4.8等)增加了一个特赞的介绍多线程的文章 :

注意:

  • 该链接以后会失效,但是 到时候你直接看Qt自带Manual就行了
  • 本文不是严格的翻译 dbzhang800 2011.06.18

使用线程

基本上有种使用线程的场合:

  • 通过利用处理器的多个核使处理速度更快。
  • 为保持GUI线程或其他高实时性线程的响应,将耗时的操作或阻塞的调用移到其他线程。

何时使用其他技术替代线程

开发人员使用线程时需要非常小心。启动线程是很容易的,但确保所有共享数据保持一致很难。遇到问题往往很难解决,这是由于在一段时间内它可能只出现一次或只在特定的硬件配置下出现。在创建线程来解决某些问题之前,应该考虑一些替代的技术 

替代技术

注解

QEventLoop::processEvents()

在一个耗时的计算操作中反复调用QEventLoop::processEvents() 可以防止界面的假死。尽管如此,这个方案可伸缩性并不太好,因为该函数可能会被调用地过于频繁或者不够频繁。

QTimer

后台处理操作有时可以方便地使用Timer安排在一个在未来的某一时刻执行的槽中来完成。在没有其他事件需要处理时,时间隔为0的定时器超时事件被相应

QSocketNotifier 
QNetworkAccessManager 
QIODevice::readyRead()

这是一个替代技术,替代有一个或多个线程在慢速网络执行阻塞读的情况。只要响应部分的计算可以快速执行,这种设计比在线程中实现的同步等待更好。与线程相比这种设计更不容易出错且更节能(energy efficient)。在许多情况下也有性能优势。

一般情况下,建议只使用安全和经过测试的方案而避免引入特设线程的概念。QtConcurrent 提供了一个将任务分发到处理器所有的核的易用接口。线程代码完全被隐藏在 QtConcurrent 框架下,所以你不必考虑细节。尽管如此,QtConcurrent 不能用于线程运行时需要通信的情况,而且它也不应该被用来处理阻塞操作。

应该使用 Qt 线程的哪种技术?

有时候,你需要的不仅仅是在另一线程的上下文中运行一个函数。您可能需要有一个生存在另一个线程中的对象来为GUI线程提供服务。也许你想在另一个始终运行的线程中来轮询硬件端口并在有关注的事情发生时发送信号到GUI线程。Qt为开发多线程应用程序提供了多种不同的解决方案。解决方案的选择依赖于新线程的目的以及线程的生命周期。

生命周期

开发任务

解决方案

一次调用

在另一个线程中运行一个函数,函数完成时退出线程

编写函数,使用QtConcurrent::run 运行它

派生QRunnable,使用QThreadPool::globalInstance()->start() 运行它

派生QThread,重新实现QThread::run() ,使用QThread::start() 运行它

一次调用

需要操作一个容器中所有的项。使用处理器所有可用的核心。一个常见的例子是从图像列表生成缩略图。

QtConcurrent 提供了map()函你数来将操作应用到容器中的每一个元素,提供了fitler()函数来选择容器元素,以及指定reduce函数作为选项来组合剩余元素。

一次调用

一个耗时运行的操作需要放入另一个线程。在处理过程中,状态信息需要发送会GUI线程。

使用QThread,重新实现run函数并根据需要发送信号。使用信号槽的queued连接方式将信号连接到GUI线程的槽函数。

持久运行

生存在另一个线程中的对象,根据要求需要执行不同的任务。这意味着工作线程需要双向的通讯。

派生一个QObject对象并实现需要的信号和槽,将对象移动到一个运行有事件循环的线程中并通过queued方式连接的信号槽进行通讯。

持久运行

生存在另一个线程中的对象,执行诸如轮询端口等重复的任务并与GUI线程通讯。

同上,但是在工作线程中使用一个定时器来轮询。尽管如此,处理轮询的最好的解决方案是彻底避免它。有时QSocketNotifer是一个替代。

Qt线程基础

QThread是一个非常便利的跨平台的对平台原生线程的抽象。启动一个线程是很简单的。让我们看一个简短的代码:生成一个在线程内输出"hello"并退出的线程。

 // hellothread/hellothread.h
 class HelloThread : public QThread
 {
     Q_OBJECT
 private:
     void run();
 };

我们从QThread派生出一个类,并重新实现run方法。

 // hellothread/hellothread.cpp
 void HelloThread::run()
 {
      qDebug() << "hello from worker thread " << thread()->currentThreadId();
 }

run方法中包含将在另一个线程中运行的代码。在本例中,一个包含线程ID的消息被打印出来。 QThread::start() 将在另一个线程中被调用。

 int main(int argc, char *argv[])
 {
     QCoreApplication app(argc, argv);
     HelloThread thread;
     thread.start();
     qDebug() << "hello from GUI thread " << app.thread()->currentThreadId();
     thread.wait();  // do not exit before the thread is completed!
     return 0;
 }

QObject与线程

QObject有线程关联(thread affinity)[如何翻译?关联?依附性?dbzhang800 20110618],换句话说,它生存于一个特定的线程。这意味着,在创建时QObject保存了到当前线程的指针。当事件使用postEvent()被派发时,这个信息变得很有用。事件被放置到相应线程的事件循环中。如果QObject所依附的线程没有事件循环,该事件将永远不会被传递。

要启动事件循环,必须在run()内调用exec()。线程关联可以通过moveToThread()来更改。

如上所述,当从其他线程调用对象的方法时开发人员必须始终保持谨慎。线程关联不会改变这种状况。 Qt文档中将一些方法标记为线程安全。postEvent()就是一个值得注意的例子。一个线程安全的方法可以同时在不同的线程被调用。

通常情况下并不会并发访问的一些方法,在其他线程调用对象的非线程安全的方法在出现造成意想不到行为的并发访问前数千次的访问可能都是工作正常的。编写测试代码不能完全确保线程的正确性,但它仍然是重要的。在Linux上,Valgrind和Helgrind有助于检测线程错误。

QThread的内部结构非常有趣:

  • QThread并不生存于执行run()的新线程内。它生存于旧线程中。
  • QThread的大多数成员方法是线程的控制接口,并设计成从旧线程中被调用。不要使用moveToThread()将该接口移动到新创建的线程中;调用moveToThread(this)被视为不好的实践。
  • exec()和静态方法usleep()、msleep()、sleep()要在新创建的线程中调用。
  • QThread子类中定义的其他成员可在两个线程中访问。开发人员负责访问的控制。一个典型的策略是在start()被调用前设置成员变量。一旦工作线程开始运行,主线程不应该操作其他成员。当工作线程终止后,主线程可以再次访问其他成员。这是一个在线程开始前传递参数并在结束后收集结果的便捷的策略。

QObject必须始终和parent在同一个线程。对于在run()中生成的对象这儿有一个惊人的后果:

 void HelloThread::run()
 {
      QObject *object1 = new QObject(this);  //error, parent must be in the same thread
      QObject object2;  // OK
      QSharedPointer <QObject> object3(new QObject); // OK
 }

使用互斥量保护数据的完整

互斥量是一个拥有lock()和unlock()方法并记住它是否已被锁定的对象。互斥量被设计为从多个线程调用。如果信号量未被锁定lock()将立即返回。下一次从另一个线程调用会发现该信号量处于锁定状态,然后lock()会阻塞线程直到其他线程调用unlock()。此功能可以确保代码段将在同一时间只能由一个线程执行。

使用事件循环防止数据破坏

Qt的事件循环对线程间的通信是一个非常有价值的工具。每个线程都可以有它自己的事件循环。在另一个线程中调用一个槽的一个安全的方法是将调用放置到另一个线程的事件循环中。这可以确保目标对象调用另一个的成员函数之前可以完成当前正在运行的成员函数。

那么,如何才能把一个成员调用放于一个事件循环中? Qt的有两种方法来做这个。一种方法是通过queued信号槽连接;另一种是使用QCoreApplication::postEvent()派发一个事件。queued的信号槽连接是异步执行的信号槽连接。内部实现是基于posted的事件。信号的参数放入事件循环后信号函数的调用将立即返回。

连接的槽函数何时被执行依赖于事件循环其他的其他操作。

通过事件循环通信消除了我们使用互斥量时所面临的死锁问题。这就是我们为什么推荐使用事件循环,而不是使用互斥量锁定对象的原因。

处理异步执行

一种获得一个工作线程的结果的方法是等待线程终止。在许多情况下,一个阻塞等待是不可接受的。阻塞等待的替代方法是异步的结果通过posted事件或者queued信号槽进行传递。由于操作的结果不会出现在源代码的下一行而是在位于源文件其他部分的一个槽中,这会产生一定的开销,因为,但在位于源文件中其他地方的槽。 Qt开发人员习惯于使用这种异步行为工作,因为它非常相似于GUI程序中使用的的事件驱动编程。

 

转载:http://blog.csdn.net/dbzhang800/article/details/6554104

分享到:
评论

相关推荐

    QT线程QThread的推荐用法

    总结来说,QT线程QThread的推荐用法包括使用`QtConcurrent::run()`来执行一次性任务,以及通过`moveToThread()`方法将QObject实例移到新线程中长期运行。这些方法简化了多线程编程,提高了程序效率,并确保了与主线...

    适合初学者的QT多线程操作的例子

    除了使用QThread直接编写多线程代码,QT还提供了`QtConcurrent`模块,它提供了一些高级的并发工具,如`run()`、`map()`和`filter()`等,可以方便地在后台线程执行函数,而无需直接管理线程。 此外,QT还支持线程池...

    qt线程传递参数.7z

    "qt线程传递参数.7z"这个压缩包很可能包含了关于如何在Qt中进行线程间参数传递的示例代码或教程。Qt提供了QThread类来支持线程操作,并且提供了一些机制来安全地在不同线程之间传递数据。 首先,我们了解下Qt中的...

    如何在QThread中使用控件

    在Qt框架中,QThread是用于实现多线程操作的关键类。然而,由于Qt的事件循环和GUI(图形用户界面)都是在主线程中运行,直接在QThread中操作控件可能会导致各种问题,比如界面卡顿、数据同步错误等。因此,了解如何...

    qt编程_在子线程中更新UI界面

    5. **Qt的并发工具**:除了QThread,Qt还提供了其他并发工具,如QFuture、QRunnable和QtConcurrent,它们简化了多线程编程,可以自动处理线程安全和线程间通信。 6. **更新UI的正确方式**:使用`QObject::...

    Qt自定义事件,Qt线程应用

    Qt线程库提供了一组类,如`QThread`、`QtConcurrent`和信号槽机制,使得在多线程环境中编写安全且高效的代码变得简单。以下是一些关键概念: 1. **QThread**:它是Qt对标准C++ `std::thread`的封装,提供了一种更...

    Qt线程操作 线程和主线程传递数据

    本文将深入探讨Qt线程操作以及线程间的数据传递。 首先,我们要理解Qt中的线程模型。Qt使用QThread类来表示线程,它提供了基本的线程操作接口,如启动、退出和等待。不同于其他编程环境,Qt并不建议直接继承QThread...

    ThreadFromQThread_QT_qt多线程_QT多线程.zip

    6. **异步编程**:QT提供的异步编程模型,如QFuture、QtConcurrent等,可以简化多线程编程,避免直接操作线程。 7. **线程池**:利用QThreadPool可以有效地管理线程资源,避免频繁创建和销毁线程的开销。 8. **...

    02_ThreadPro_QT多线程_QT实例_QT_QT多线程_线程_

    首先,我们了解QT中的线程基础。在QT中,主线程通常负责UI更新,而工作线程则执行耗时操作,以避免阻塞UI。线程类`QThread`是QT提供的核心组件,它允许我们创建和管理自定义线程。创建一个`QThread`对象,然后重载`...

    qt_multiThread.rar_QT 多线程_qt多线程

    - 除了使用QThread,Qt还提供了基于QtConcurrent的异步编程模型,它允许开发者以更简洁的方式执行耗时操作,如mapReduce,同时避免了多线程的复杂性。 7. **线程安全的Qt对象**: - 部分Qt对象如QMutex、...

    Qt多线程经典例子

    - **共享数据**:在多线程环境中,访问共享数据需要同步,Qt提供了`QMutex`、`QReadWriteLock`等同步工具。 - **事件循环**:每个线程都可以有自己的事件循环,通过`QEventLoop`管理。`QThread::exec()`启动事件...

    Qt多线程服务器

    Qt除了提供QThread之外,还引入了QRunnable和QtConcurrent库,用于简化线程任务的创建和执行。QRunnable是一个抽象类,可以包含任何可执行的代码,而QtConcurrent则提供了方便的函数来并发执行这些任务,无需直接...

    Qt5串口线程

    此外,为了优化性能和资源管理,可以考虑使用Qt的异步编程模型,如QFuture和QtConcurrent,它们可以让你在不直接使用线程的情况下实现并发操作。这种方式更简洁,且避免了直接管理线程的复杂性。 最后,调试多线程...

    QT多线程小例子.zip

    QT5还引入了QtConcurrent库,提供了一组方便的函数,如run()、mapped()等,可以简化并行计算的任务,无需直接处理线程细节。这些函数会自动在合适的线程池中执行,提高了代码的可读性和可维护性。 总的来说,QT5的...

    QT的多线程编程示例

    8. **线程池**:在大型项目中,使用线程池(如QT的QtConcurrent模块)可以更高效地管理和复用线程,避免频繁创建和销毁线程的开销。 这个示例程序很可能会展示如何创建一个新的QThread实例,定义工作逻辑,然后使用...

    Qt 线程池(QThreadPool )的使用

    `QThread`提供了基础的线程操作,而`QtConcurrent`则简化了多线程编程,尤其是与Qt对象交互的部分。 `QThreadPool`是QtConcurrent的一部分,它管理一组可重用的线程。要使用线程池,首先需要包含`&lt;QtConcurrent&gt;`...

    Qt多线程的使用示例,演示QThread的run-start,QObject的moveToThread,QtConcurrent等多线程处理方法

    对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同...

    qt4基础教程

    11. **并发编程**:Qt4通过QThread和QtConcurrent类支持多线程和并行计算,帮助开发者充分利用多核处理器的优势。 12. **XML处理**:Qt提供QDomDocument和QXmlStreamReader等类,用于解析和生成XML文档。 学习Qt4...

    用QT实现的进程线程,线程同步和线程互斥程序

    在QT中,我们还可以使用`QFuture`、`QFutureWatcher`和`QtConcurrent`模块来实现异步计算,这些工具可以将计算任务分配到不同的线程中,提高程序的并行性,而主线程可以继续处理用户界面事务。 线程通信在QT中通常...

    learn-qt-concurrent.zip

    Qt多线程的另外一种方式:Qt_Concurrent .pro QT += concurrent CONFIG += c++11 .h #include &lt;QtConcurrent/QtConcurrent&gt; #include &lt;QThread&gt; .cpp qDebug() 主线程" &lt;&lt; QThread::currentThread(); ...

Global site tag (gtag.js) - Google Analytics