基本上有种使用线程的场合: html
开发人员使用线程时须要很是当心。启动线程是很容易的,但确保全部共享数据保持一致很难。遇到问题每每很难解决,这是因为在一段时间内它可能只出现一次或只在特定的硬件配置下出现。在建立线程来解决某些问题以前,应该考虑一些替代的技术 : 编程
替代技术 安全 |
注解 网络 |
QEventLoop::processEvents() 多线程 |
在一个耗时的计算操做中反复调用QEventLoop::processEvents() 能够防止界面的假死。尽管如此,这个方案可伸缩性并不太好,由于该函数可能会被调用地过于频繁或者不够频繁。 并发 |
QTimer app |
后台处理操做有时能够方便地使用Timer安排在一个在将来的某一时刻执行的槽中来完成。在没有其余事件须要处理时,时间隔为0的定时器超时事件被相应 框架 |
QSocketNotifier |
这是一个替代技术,替代有一个或多个线程在慢速网络执行阻塞读的状况。只要响应部分的计算能够快速执行,这种设计比在线程中实现的同步等待更好。与线程相比这种设计更不容易出错且更节能(energy efficient)。在许多状况下也有性能优点。 函数式编程 |
通常状况下,建议只使用安全和通过测试的方案而避免引入特设线程的概念。QtConcurrent 提供了一个将任务分发处处理器全部的核的易用接口。线程代码彻底被隐藏在 QtConcurrent 框架下,因此你没必要考虑细节。尽管如此,QtConcurrent 不能用于线程运行时须要通讯的状况,并且它也不该该被用来处理阻塞操做。
有时候,你须要的不只仅是在另外一线程的上下文中运行一个函数。您可能须要有一个生存在另外一个线程中的对象来为 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是一个替代。 |
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有线程关联(thread affinity)[如何翻译?关联?依附性?dbzhang800 20110618],换句话说,它生存于一个特定的线程。这意味着,在建立时QObject保存了到当前线程的指针。当事件使用postEvent()被 派发时,这个信息变得颇有用。事件被放置到相应线程的事件循环中。若是QObject所依附的线程没有事件循环,该事件将永远不会被传递。
要启动事件循环,必须在run()内调用exec()。线程关联能够经过moveToThread()来更改。
如上所述,当从其余线程调用对象的方法时开发人员必须始终保持谨慎。线程关联不会改变这种情况。 Qt文档中将一些方法标记为线程安全。postEvent()就是一个值得注意的例子。一个线程安全的方法能够同时在不一样的线程被调用。
一般状况下并不会并发访问的一些方法,在其余线程调用对象的非线程安全的方法在出现形成意想不到行为的并发访问前 数千次的访问可能都是工做正常的。编写测试代码不能彻底确保线程的正确性,但它仍然是重要的。在Linux上,Valgrind和Helgrind有助于 检测线程错误。
QThread的内部结构很是有趣:
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程序中使用的的事件驱动编程。
***************************************************************************************************
1、QThreadPool类
QThreadPool管理一组线程。它负责管理和回收单个QThread对象以减小程序中线程建立的开销。每一个Qt应用程序都有一个全局的QThreadPool对象,可经过方法globalInstance()得到。为了调用QThreadPool中的一个线程,须要提供一个从QRunnable继承过来的类,并实现其中的run方法。而后建立一个该类的对象,传递给QThreadPool::start()方法。代码片段以下:
默认状况下, QThreadPool自动删除QRunnable对象。使用QRunnable::setAutoDelete()方法能够改变该默认行为。QThreadPool支持在QRunnable::run方法中经过调用tryStart(this)来屡次执行相同的QRunnable。当最后一个线程退出run函数后,若是autoDelete启用的话,将删除QRunnable对象。在autoDelete启用的状况下,调用start()方法屡次执行同一QRunnable会产生竞态,就避免这样作。
那些在必定时间内会使用的线程将会过时。默认的过时时间是30秒。可经过setExpiryTimeout()方法来设置。设置一个负数的超时值表明禁用超时机制。方法maxThreadCount()能够查询可以使用的最大线程数,你也能够设置最大的线程数。activeThreadCount反应的是当前正在被使用中的线程数个数。reserveThread函数保留某个线程为外部使用,releaseThread释放该线程,这样就能够被再次使用。
2、QtConcurrent命名空间
QtConcurrent命名空间里提供了一些高级API,利用这些API能够编写多线程程序,而不用直接使用比较低级的一些类,如mutext,lock, waitcondition以及semaphore等。使用QtConcurrent命令空间的API编写的程序会根据处理器的数目自动地调整线程的个数。QtConcurrent包含了用于并行列表处理的函数式编程,包含实现共享内存系统的MapReduce和FilterReduce, 以及管理GUI应用程序中异步计算的类。相关的类说明以下:
appliesa function to every item in a container, modifying the itemsin-place |
|
islike map(), except that it returns a new container with themodifications |
|
islike mapped(), except that the modified results are reduced orfolded into a single result. |
|
litems from a container based on the result of a filter function. |
|
islike filter(), except that it returns a new container with thefiltered results |
|
islike filtered(), except that the filtered results are reduced orfolded into a single result |
|
runsa function in another thread. |
|
allowsiterating through results available via QFuture. |
|
allowsmonitoring a QFuture usingsignals-and-slots. |
|
isa convenience class that automatically synchronizes severalQFutures. |
代码实例:
**********************************************************************************************************
QFuture<T> run(const Class *object, T (Class::*fn)(Param1, Param2, Param3, Param4, Param5) const, const Arg1 &arg1,const Arg2 &arg2, const Arg3 &arg3, const Arg4 &arg4, const Arg5 &arg5)
run()函数的原型如上,此函数是QtConcurrent命名空间里的函数.主要功能是令启动一个线程来执行一个函数.Concurrent的英文示意就是并发的意思.
下面简要的介绍run()函数的使用方法:
1.首先要有一个须要在另外开启的线程中执行的函数:
void thread_add(QObject *receiver,int a,int b)
{
QString message=QString("%1 + %2 = %3").arg(a).arg(b).arg(a+b);
QApplication::postEvent(receiver,new ProgressEvent(true, message));
}
函数在线程中运行完毕后会向receiver发送一个消息,来返回结果.
2.有了要在线程中运行的函数,再来看看怎么启动线程来运行这个函数
void MainWindow::on_pushButton_clicked()
{
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
QtConcurrent::run(thread_add,this,i,j);
}
点击一个按钮就会运行这段代码,而后启动8*8=64个线程,线程要运行的函数就是thread_add(以前定义的),消息接收对象就是MainWindow这个类的实例
3.线程获得了运行会发送消息给MainWindow,MainWindow从新实现bool MainWindow::event ( QEvent * event )处理接收到的消息,并显示出来
bool MainWindow::event ( QEvent * event )
{
if (event->type() ==
static_cast<QEvent::Type>(ProgressEvent::EventId)) {
ProgressEvent *progressEvent =
static_cast<ProgressEvent*>(event);
Q_ASSERT(progressEvent);
ui->teLog->append(progressEvent->message);
return true;
}
return QMainWindow::event(event);
}
再给出自定义的消息结构
struct ProgressEvent : public QEvent
{
enum {EventId = QEvent::User};
explicit ProgressEvent(bool saved_, const QString &message_)
: QEvent(static_cast<Type>(EventId)),
saved(saved_), message(message_) {}
const bool saved;
const QString message;
};