Qt 多线程探秘1--QThread应用

来源:互联网 发布:库存销售软件 编辑:程序博客网 时间:2024/05/29 04:55

起因

    从事软件开发多年,对于多线程的使用一直没有深究,每次用到只是找找api调用,自己却没有深追。最近看到很多多线程的例子,有Qt上的实现,有通过c++11来进行实现,深感其中的门道,遂打算细细剖析。

Qt中QThread的几种用法及源码剖析

参考博文

附上之前参考的博文,和从项目中学到的颇为类似,文章中写的Qt中线程的两种使用方法,分析得也很好 http://blog.csdn.net/u013686019/article/details/38851353。

从源码入手

查看Qt中QThread实现:附上关键代码。

void QThread::start(Priority priority){...if (data->eventDispatcher.load()) // custom event dispatcher set?        data->eventDispatcher.load()->startingUp();    else        createEventDispatcher(data); // 不存在EventDispatcher则创建,所以可以在start之前通过QThread::setEventDispatcher设置自己的EventDispatcher#ifndef Q_OS_WINRT    d->handle = (Qt::HANDLE) _beginthreadex(NULL, d->stackSize, QThreadPrivate::start,                                            this, CREATE_SUSPENDED, &(d->id));#else // !Q_OS_WINRT    d->handle = (Qt::HANDLE) CreateThread(NULL, d->stackSize, (LPTHREAD_START_ROUTINE)QThreadPrivate::start,                                            this, CREATE_SUSPENDED, reinterpret_cast<LPDWORD>(&d->id));#endif // Q_OS_WINRT...}

查看代码后发现最终还是调用_beginthreadex或者CreateThread(这里只分析windows上的实现)。注意一般需要优先调用_beginthreadex(该函数最终还是会调用CreateThread,只不过之前会做一些类似线程本地存储的工作参考:http://blog.csdn.net/morewindows/article/details/7421759#reply)。理解了QThread做了什么事情,自然可以猜测到QThread::wait()无非是调用WaitForSingleObject之类的。
最终把矛头对向QThreadPrivate::start:

unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg){    QThread *thr = reinterpret_cast<QThread *>(arg);    QThreadData *data = QThreadData::get2(thr);    qt_create_tls();    TlsSetValue(qt_current_thread_data_tls_index, data);    data->threadId = reinterpret_cast<Qt::HANDLE>(quintptr(GetCurrentThreadId()));    QThread::setTerminationEnabled(false);    {        QMutexLocker locker(&thr->d_func()->mutex);        data->quitNow = thr->d_func()->exited;    }    if (data->eventDispatcher.load()) // custom event dispatcher set?        data->eventDispatcher.load()->startingUp();    else        createEventDispatcher(data);#if !defined(QT_NO_DEBUG) && defined(Q_CC_MSVC) && !defined(Q_OS_WINCE) && !defined(Q_OS_WINRT)    // sets the name of the current thread.    QByteArray objectName = thr->objectName().toLocal8Bit();    qt_set_thread_name((HANDLE)-1,                       objectName.isEmpty() ?                       thr->metaObject()->className() : objectName.constData());#endif    emit thr->started(QThread::QPrivateSignal());    QThread::setTerminationEnabled(true);    thr->run();    finish(arg);    return 0;}

查看源码,有以下几点值得注意:1.qt_set_thread_name相关代码可以知道,Qt将会将线程名设置为QThread的objectName或者QThread子类的类名,这点在QThread相关帮助中也有阐述 :

To choose the name that your thread will be given (as identified by the command ps -L on Linux, for example), you can call setObjectName() before starting the thread. If you don’t call setObjectName(), the name given to your thread will be the class name of the runtime type of your thread object (for example, “RenderThread” in the case of the Mandelbrot Example, as that is the name of the QThread subclass). Note that this is currently not available with release builds on Windows.

通过设置objectName可以便于调试以及在调用函数执行前检测是否在所需线程中执行。
2.QThreadPrivate::start最后几行表明start最终还是会调用QThread::run(virtual void run();虚的保护方法)。

接着需要分析QThread::run,查看代码可知默认最终启动一个QEventLoop 执行事件循环。

void QThread::run(){    (void) exec(); // 这里exec是保护方法,却不是虚的}int QThread::exec(){    Q_D(QThread);    QMutexLocker locker(&d->mutex);    d->data->quitNow = false;    if (d->exited) {        d->exited = false;        return d->returnCode;    }    locker.unlock();    QEventLoop eventLoop;    int returnCode = eventLoop.exec();    locker.relock();    d->exited = false;    d->returnCode = -1;    return returnCode;}

顺便多分析下QEventLoop,代码比较简单,摘抄比较重要的一段:

int QEventLoop::exec(ProcessEventsFlags flags){...    while (!d->exit.loadAcquire())        processEvents(flags | WaitForMoreEvents | EventLoopExec);    ref.exceptionCaught = false;    return d->returnCode.load();}bool QEventLoop::processEvents(ProcessEventsFlags flags){    Q_D(QEventLoop);    if (!d->threadData->eventDispatcher.load())        return false;    return d->threadData->eventDispatcher.load()->processEvents(flags);}

可以通过void QThread::setEventDispatcher(QAbstractEventDispatcher *eventDispatcher)改变EventDispatcher。

源码分析总结:

针对之前提到博文中的用法和源码剖析的结果可以总结为两种是用QThread的方式:
1. 简单直接,继承子QThread并重载run方法(exec为非虚的无法重载),重载有一点特别需要注意即参考中文章所说,假设实现的类为ThreadA,则因为ThreadA存在于创建线程,如果通过信号连接则槽函数的执行位置在ThreadA被创建的线程内和ThreadA代表的线程没有关系;
2. 通过事件循环及当前线程中的对象,利用信号和槽的机制来实现在QThread线程执行方法。

尤其注意:

尤其需要注意QThread析构的问题Note that deleting a QThread object will not stop the execution of the thread it manages. Deleting a running QThread (i.e. isFinished() returns false) will probably result in a program crash. Wait for the finished() signal before deleting the QThread. Qt帮助中也有所提醒,这种错误将极难发现,出错点并不固定,而且往往导致crash,需要特别注意。
以下有意制造了一个这样的crash,详细分析原由:当QThread已经析构,而当前线程仍然在执行,访问到了被析构的对象,导致不可预测的问题,以后遇到类似问题可以留意是该对象已经被析构(具体表现为对象内部的值为一些无效的乱值)。
测试代码如下:

QtGuiApplication1::QtGuiApplication1(QWidget *parent)    : QMainWindow(parent){    MyThread1 t1;    t1.start();    { // 主要看这里        QThread t;        t.setObjectName("testThread");        t.start();    }    QThread t;    t.setObjectName("testThread");    t.start();    ui.setupUi(this);}

当前直接采用随意新建的Qt项目(在vs下进行调试,Qt Creator中类似),注意代码中大括号中内容,QThread t;出了大括号即被析构,导致该线程运行出错。
这里写图片描述
从以上截图崩溃其实就源于QThread 已经析构,程序仍然访问它内部的成员所致,同样代码再次运行后再次截图。
这里写图片描述
比较两张图,调用堆栈展开发生了变化,这便是所说的不可预测的问题,另外图2中也展示了某些对象内部出现乱值,这也是被破坏的一个很重要的表现。
对于上面的问题也不是不能解决,但是还是可能会有问题,解决方法及在QThread被析构前先调用quit(),再次调用wait()(这里主要的问题是wait会阻塞当前线程…,另外不能只调用quit即去析构QThread,因为quit并不是立即结束对应的QThread,而是需要等待到达下一个事件循环结束进程,立即析构QThread也会导致崩溃),类似如下代码:

    {        QThread t;        t.setObjectName("mm");        t.start();        t.quit();        t.wait();    }

综上所述,QThread使用最好还是通过new产生,并且通过与finished()连接的槽来删除线程对象。

0 0
原创粉丝点击