QT开发(三十四)——QT多线程编程

QT开发(三十四)——QT多线程编程

1、线程基础

1GUI线程与工做线程

每一个程序启动后拥有的第一个线程称为主线程,即GUI线程。QT中全部的组件类和几个相关的类只能工做在GUI线程,不能工做在次线程,次线程即工做线程,主要负责处理GUI线程卸下的工做。数据库

二、数据的同步访问

每一个线程都有本身的栈,所以每一个线程都要本身的调用历史和本地变量。线程共享相同的地址空间。编程

2、QT多线程简介

    QT经过三种形式提供了对线程的支持,分别是平台无关的线程类、线程安全的事件投递、跨线程的信号-槽链接。安全

    QT中线程类包含以下:网络

 QThread 提供了跨平台的多线程解决方案多线程

 QThreadStorage 提供逐线程数据存储
    QMutex 提供相互排斥的锁,或互斥量
    QMutexLocker 是一个辅助类,自动对 QMutex 加锁与解锁
    QReadWriterLock 提供了一个能够同时读操做的锁
    QReadLocker与QWriteLocker 自动对QReadWriteLock 加锁与解锁
    QSemaphore 提供了一个整型信号量,是互斥量的泛化
    QWaitCondition 提供了一种方法,使得线程能够在被另外线程唤醒以前一直休眠。并发

3、QThread线程

1QThread线程基础

    QThreadQt线程中有一个公共的抽象类,全部的线程类都是从QThread抽象类中派生的,须要实现QThread中的虚函数run(),经过start()函数来调用run函数框架

    void run()函数是线程体函数,用于定义线程的功能。less

    void start()函数是启动函数,用于将线程入口地址设置为run函数。异步

    void terminate()函数用于强制结束线程,不保证数据完整性和资源释放。socket

    QCoreApplication::exec()老是在主线程(执行main()的线程)中被调用,不能从一个QThread中调用。在GUI程序中,主线程也称为GUI线程,是惟一容许执行GUI相关操做的线程。另外,必须在建立一个QThread前建立QApplication(or QCoreApplication)对象。

    当线程启动和结束时,QThread会发送信号started()和finished(),可使用isFinished()和isRunning()来查询线程的状态。

    从Qt4.8起,能够释放运行刚刚结束的线程对象,经过链接finished()信号到QObject::deleteLater()槽。
    使用wait()来阻塞调用的线程,直到其它线程执行完毕(或者直到指定的时间过去)。

    静态函数currentThreadId()和currentThread()返回标识当前正在执行的线程。前者返回线程的ID,后者返回一个线程指针。

    要设置线程的名称,能够在启动线程以前调用setObjectName()。若是不调用setObjectName(),线程的名称将是线程对象的运行时类型(QThread子类的类名)。

2、线程的优先级

    QThread线程总共有8个优先级

    QThread::IdlePriority   0 scheduled only when no other threads are running.

    QThread::LowestPriority  1 scheduled less often than LowPriority.

    QThread::LowPriority   2 scheduled less often than NormalPriority.

    QThread::NormalPriority  3 the default priority of the operating system.

    QThread::HighPriority   4 scheduled more often than NormalPriority.

    QThread::HighestPriority  5 scheduled more often than HighPriority.

    QThread::TimeCriticalPriority 6 scheduled as often as possible.

    QThread::InheritPriority   7 use the same priority as the creating thread. This is the default.

    void setPriority(Priority priority)
    设置正在运行线程的优先级。若是线程没有运行,此函数不执行任何操做并当即返回。使用的start()来启动一个线程具备特定的优先级。优先级参数能够是QThread::Priority枚举除InheritPriortyd的任何值。

3、线程的建立

   void start ( Priority priority = InheritPriority )

    启动线程执行,启动后会发出started ()信号

4、线程的执行

int exec() [protected]
    进入事件循环并等待直到调用exit(),返回值是经过调用exit()来得到,若是调用成功则返回0。

void run() [virtual protected]
    线程的起点,在调用start()以后,新建立的线程就会调用run函数,默认实现调用exec(),大多数须要从新实现run函数,便于管理本身的线程。run函数返回时,线程的执行将结束。

5、线程的退出

void quit();

通知线程事件循环退出,返回0表示成功,至关于调用了QThread::exit(0)。

void exit ( int returnCode = 0 );

调用exit后,thread将退出event loop,并从exec返回,exec的返回值就是returnCode。一般returnCode=0表示成功,其余值表示失败。

void terminate ();

    结束线程,线程是否当即终止取决于操做系统。

    线程被终止时,全部等待该线程Finished的线程都将被唤醒。

    terminate是否调用取决于setTerminationEnabled ( bool enabled = true )开关。

    void requestInterruption()
    请求线程的中断。请求是咨询意见而且取决于线程上运行的代码,来决定是否及如何执行这样的请求。此函数不中止线程上运行的任何事件循环,而且在任何状况下都不会终止它。

    工程中线程退出的解决方案以下:

    经过在线程类中增长标识变量volatile bool m_stop,经过m_stop变量的值判断run函数是否执行结束返回。

#ifndef WORKTHREAD_H
#define WORKTHREAD_H
#include <QThread>
#include <QDebug>
 
class WorkThread : public QThread
{
protected:
  //线程退出的标识量
  volatile bool m_stop;
  void run()
  {
    qDebug() << "run begin";
    while(!m_stop)
    {
        //task handling
        int* p = new int[1000];
        for(int i = 0; i < 1000; i++)
        {
            p[i] = i * i;
        }
        sleep(2);
        delete [] p;
    }
    qDebug() << "run end";
  }
public:
  WorkThread()
  {
    m_stop = false;
  }
  //线程退出的接口函数,用户使用
  void stop()
  {
    m_stop = true;
  }
};
 
#endif // WORKTHREAD_H


6、线程的等待

bool wait ( unsigned long time = ULONG_MAX )

    线程将会被阻塞,等待time毫秒,若是线程退出,则wait会返回。Wait函数解决多线程在执行时序上的依赖。

void msleep ( unsigned long msecs )

void sleep ( unsigned long secs )

void usleep ( unsigned long usecs )

    sleep()、msleep()、usleep()容许秒,毫秒和微秒来区分,但在Qt5.0中被设为public。

    通常状况下,wait()和sleep()函数应该不须要,由于Qt是一个事件驱动型框架。考虑监听finished()信号来取代wait(),使用QTimer来取代sleep()。

7、线程的状态

bool isFinished () const  线程是否已经退出

bool isRunning () const   线程是否处于运行状态

8、线程的属性

Priority priority () const

void setPriority ( Priority priority )

uint stackSize () const

void setStackSize ( uint stackSize )

void setTerminationEnabled ( bool enabled = true )

设置是否响应terminate()函数

9、线程与事件循环

    QThreadrun()的默认实现调用了exec(),从而建立一个QEventLoop对象,由QEventLoop对象处理线程中事件队列(每个线程都有一个属于本身的事件队列)中的事件。exec()在其内部不断作着循环遍历事件队列的工做,调用QThreadquit()exit()方法使退出线程,尽可能不要使用terminate()退出线程,terminate()退出线程过于粗暴,形成资源不能释放,甚至互斥锁还处于加锁状态。

    线程中的事件循环,使得线程可使用那些须要事件循环的非GUI 类(如,QTimer,QTcpSocket,QProcess)。

    在QApplication前建立的对象,QObject::thread()返回NULL,意味着主线程仅为这些对象处理投递事件,不会为没有所属线程的对象处理另外的事件。能够用QObject::moveToThread()来改变对象及其子对象的线程亲缘关系,假如对象有父亲,不能移动这种关系。在另外一个线程(而不是建立它的线程)中delete QObject对象是不安全的。除非能够保证在同一时刻对象不在处理事件。能够用QObject::deleteLater(),它会投递一个DeferredDelete事件,这会被对象线程的事件循环最终选取到。假如没有事件循环运行,事件不会分发给对象。假如在一个线程中建立了一个QTimer对象,但从没有调用过exec(),那么QTimer就不会发射它的timeout()信号deleteLater()也不会工做。能够手工使用线程安全的函数QCoreApplication::postEvent(),在任什么时候候,给任何线程中的任何对象投递一个事件,事件会在那个建立了对象的线程中经过事件循环派发。事件过滤器在全部线程中也被支持,不过它限定被监视对象与监视对象生存在同一线程中。QCoreApplication::sendEvent(不是postEvent()),仅用于在调用此函数的线程中向目标对象投递事件。

4、线程的同步

1、线程同步基础

    临界资源:每次只容许一个线程进行访问的资源

    线程间互斥:多个线程在同一时刻都须要访问临界资源

    线程锁可以保证临界资源的安全性,一般,每一个临界资源须要一个线程锁进行保护。

    线程死锁:线程间相互等待临界资源而形成彼此没法继续执行。

    产生死锁的条件:

    A、系统中存在多个临界资源且临界资源不可抢占

    B、线程须要多个临界资源才能继续执行

    死锁的避免:

    A、对使用的每一个临界资源都分配一个惟一的序号

    B、对每一个临界资源对应的线程锁分配相应的序号

    C、系统中的每一个线程按照严格递增的次序请求临界资源

    QMutex, QReadWriteLock, QSemaphore, QWaitCondition 提供了线程同步的手段。使用线程的主要想法是但愿它们能够尽量并发执行,而一些关键点上线程之间须要中止或等待。例如,假如两个线程试图同时访问同一个全局变量,结果可能不如所愿。

2、互斥量QMutex

    QMutex 提供相互排斥的锁,或互斥量。在一个时刻至多一个线程拥有mutex,假如一个线程试图访问已经被锁定的mutex,那么线程将休眠,直到拥有mutex的线程对此mutex解锁。QMutex经常使用来保护共享数据访问。QMutex类因此成员函数是线程安全的。

   头文件声明:    #include <QMutex>

互斥量声明:    QMutex m_Mutex;

互斥量加锁:    m_Mutex.lock();

   互斥量解锁:    m_Mutex.unlock();

    若是对没有加锁的互斥量进行解锁,结果是未定义的。互斥量的加锁和解锁必须在同一线程中成对出现。

    QMutex ( RecursionMode mode = NonRecursive )

    QMutex有两种模式:Recursive, NonRecursive

ARecursive

    一个线程能够对mutex屡次lock,直到相应次数的unlock调用后,mutex才真正被解锁。

BNonRecursive

    默认模式,mutex只能被lock一次。

    若是使用了Mutex.lock()而没有对应的使用Mutex.unlcok()的话就会形成死锁,其余的线程将永远也得不到接触Mutex锁住的共享资源的机会。尽管能够不使用lock()而使用tryLock(timeout)来避免由于死等而形成的死锁( tryLock(负值)==lock()),可是仍是颇有可能形成错误。

    bool tryLock();

    若是当前其余线程已对该mutex加锁,则该调用会当即返回,而不被阻塞。

    bool tryLock(int timeout);

    若是当前其余线程已对该mutex加锁,则该调用会等待一段时间,直到超时

QMutex mutex;
int complexFunction(int flag)
 {
     mutex.lock();
     int retVal = 0;
     switch (flag) {
     case 0:
     case 1:
         mutex.unlock();
         return moreComplexFunction(flag);
     case 2:
         {
             int status = anotherFunction();
             if (status < 0) {
                 mutex.unlock();
                 return -2;
             }
             retVal = status + flag;
         }
         break;
     default:
         if (flag > 10) {
             mutex.unlock();
             return -1;
         }
         break;
     }
 
     mutex.unlock();
     return retVal;
 }


3、互斥锁QMutexLocker

    在较复杂的函数和异常处理中对QMutex类mutex对象进行lock()和unlock()操做将会很复杂,进入点要lock(),在全部跳出点都要unlock(),很容易出如今某些跳出点未调用unlock(),因此Qt引进了QMutex的辅助类QMutexLocker来避免lock()和unlock()操做。在函数须要的地方创建QMutexLocker对象,并把mutex指针传QMutexLocker对象,此时mutex已经加锁,等到退出函数后,QMutexLocker对象局部变量会本身销毁,此时mutex解锁。

头文件声明:    #include<QMutexLocker>

互斥锁声明:    QMutexLocker mutexLocker(&m_Mutex);

互斥锁加锁:    从声明处开始(在构造函数中加锁)

互斥锁解锁:    出了做用域自动解锁(在析构函数中解锁)

QMutex mutex;
 int complexFunction(int flag)
 {
     QMutexLocker locker(&mutex);
     int retVal = 0;
     switch (flag) {
     case 0:
     case 1:
         return moreComplexFunction(flag);
     case 2:
         {
             int status = anotherFunction();
             if (status < 0)
                 return -2;
             retVal = status + flag;
         }
         break;
     default:
         if (flag > 10)
             return -1;
         break;
     }
     return retVal;
 }


4QReadWriteLock

    QReadWriterLock 与QMutex类似,但对读写操做访问进行区别对待,能够容许多个读者同时读数据,但只能有一个写,而且写读操做不一样同时进行。使用QReadWriteLock而不是QMutex,可使得多线程程序更具备并发性。 QReadWriterLock默认模式是NonRecursive

QReadWriterLock类成员函数以下:

QReadWriteLock ( )

QReadWriteLock ( RecursionMode recursionMode )

void lockForRead ()

void lockForWrite ()

bool tryLockForRead ()

bool tryLockForRead ( int timeout )

bool tryLockForWrite ()

bool tryLockForWrite ( int timeout )

boid unlock ()

使用实例:

 

QReadWriteLock lock;
 void ReaderThread::run()
 {
     lock.lockForRead();
     read_file();
     lock.unlock();
 }
 
 void WriterThread::run()
 {
     lock.lockForWrite();
     write_file();
     lock.unlock();
 }


5QReadLockerQWriteLocker

    在较复杂的函数和异常处理中对QReadWriterLocklock对象进行lockForRead()/lockForWrite()和unlock()操做将会很复杂,进入点要lockForRead()/lockForWrite(),在全部跳出点都要unlock(),很容易出如今某些跳出点未调用unlock(),因此Qt引进了QReadLocker和QWriteLocker类来简化解锁操做。在函数须要的地方创建QReadLockerQWriteLocker对象,并把lock指针传给QReadLockerQWriteLocker对象,此时lock已经加锁,等到退出函数后,QReadLockerQWriteLocker对象局部变量会本身销毁,此时lock解锁。

  

QReadWriteLock lock;
 QByteArray readData()
 {
     lock.lockForRead();
     ...
     lock.unlock();
     return data;
 }


使用QReadLocker

 

QReadWriteLock lock;
 QByteArray readData()
 {
     QReadLocker locker(&lock);
     ...
     return data;
 }


6、信号量QSemaphore

    QSemaphore 是QMutex的通常化,是特殊的线程锁,容许多个线程同时访问临界资源,而一个QMutex只保护一个临界资源。QSemaphore 类的全部成员函数是线程安全的。

    经典的生产者-消费者模型以下:某工厂只有固定仓位,生产人员天天生产的产品数量不一,销售人员天天销售的产品数量也不一致。当生产人员生产P个产品时,就一次须要P个仓位,当销售人员销售C个产品时,就要求仓库中有足够多的产品才能销售。若是剩余仓位没有P个时,该批次的产品都不存入,当当前已有的产品没有C个时,就不能销售C个以上的产品,直到新产品加入后方可销售。

    QSemaphore来控制对环状缓冲的访问,此缓冲区被生产者线程和消费者线程共享。生产者不断向缓冲区写入数据直到缓冲末端,再从头开始。消费者从缓冲不断读取数据。信号量比互斥量有更好的并发性,假如咱们用互斥量来控制对缓冲的访问,那么生产者、消费者不能同时访问缓冲区。然而,咱们知道在同一时刻,不一样线程访问缓冲的不一样部分并无什么危害。

QSemaphore 类成员函数:

QSemaphore ( int n = 0 )

void acquire ( int n = 1 )

int available () const

void release ( int n = 1 )

bool tryAcquire ( int n = 1 )

bool tryAcquire ( int n, int timeout )

实例代码:

 QSemaphore sem(5);      // sem.available() == 5

 sem.acquire(3);         // sem.available() == 2

 sem.acquire(2);         // sem.available() == 0

 sem.release(5);         // sem.available() == 5

 sem.release(5);         // sem.available() == 10

 sem.tryAcquire(1);      // sem.available() == 9, returns true

 sem.tryAcquire(250);    // sem.available() == 9, returns false

生产者-消费者实例:

#include <QtCore/QCoreApplication>

#include <QSemaphore>

#include <QThread>

#include <cstdlib>

#include <cstdio>

const int DataSize = 100000;

const int BufferSize = 8192;

char buffer[BufferSize];

QSemaphore  production(BufferSize);

QSemaphore  consumption;

class Producor:public QThread

{

public:

    void run();

};

void Producor::run()

{

    for(int i = 0; i < DataSize; i++)

    {

        production.acquire();

        buffer[i%BufferSize] = "ACGT"[(int)qrand()%4];

        consumption.release();

    }

}

class Consumer:public QThread

{

public:

    void run();

};

void Consumer::run()

{

    for(int i = 0; i < DataSize; i++)

    {

        consumption.acquire();

        fprintf(stderr, "%c", buffer[i%BufferSize]);

        production.release();

    }

    fprintf(stderr, "%c", "\n");

}

int main(int argc, char *argv[])

{

    QCoreApplication a(argc, argv);

    Producor productor;

    Consumer consumer;

    productor.start();

    consumer.start();

    productor.wait();

    consumer.wait();

    return a.exec();

}

Producer::run函数:

   当producer线程执行run函数,若是buffer中已满,而consumer线程没有读,producer不能再往buffer中写字符, productor.acquire 处阻塞直到 consumer线程读(consume)数据。一旦producer获取到一个字节(资源)就写入一个随机的字符,并调用 consumer.release 使consumer线程能够获取一个资源(读一个字节的数据)。

    Consumer::run函数:

   当consumer线程执行run函数,若是buffer中没有数据,则consumer线程在consumer.acquire处阻塞,直到producer线程执行写操做写入一个字节,并执行consumer.release 使consumer线程的可用资源数=1时,consumer线程从阻塞状态中退出, 并将consumer 资源数-1,consumer当前资源数=0。

7、等待条件QWaitCondition

    QWaitCondition 容许线程在某些状况发生时唤醒另外的线程。一个或多个线程能够阻塞等待QWaitCondition ,用wakeOne()或wakeAll()设置一个条件。wakeOne()随机唤醒一个,wakeAll()唤醒全部。

QWaitCondition ()

bool wait ( QMutex * mutex, unsigned long time = ULONG_MAX )

bool wait ( QReadWriteLock * readWriteLock, unsigned long time = ULONG_MAX )

void wakeOne ()

void wakeAll ()

头文件声明:    #include <QWaitCondition>

等待条件声明:    QWaitCondtion m_WaitCondition;

等待条件等待:    m_WaitConditon.wait(&m_muxtex, time);

等待条件唤醒:    m_WaitCondition.wakeAll();

在经典的生产者-消费者场合中,生产者首先必须检查缓冲是否已满(numUsedBytes==BufferSize),若是缓冲区已满,线程停下来等待 bufferNotFull条件。若是没有满,在缓冲中生产数据,增长numUsedBytes,激活条件 bufferNotEmpty。使用mutex来保护对numUsedBytes的访问。QWaitCondition::wait() 接收一个mutex做为参数,mutex被调用线程初始化为锁定状态。在线程进入休眠状态以前,mutex会被解锁。而当线程被唤醒时,mutex会处于锁定状态,从锁定状态到等待状态的转换是原子操做。当程序开始运行时,只有生产者能够工做,消费者被阻塞等待bufferNotEmpty条件,一旦生产者在缓冲中放入一个字节,bufferNotEmpty条件被激发,消费者线程因而被唤醒。

#include <QtCore/QCoreApplication>

#include <QSemaphore>

#include <QThread>

#include <cstdlib>

#include <cstdio>

#include <QWaitCondition>

#include <QMutex>

#include <QTime>

const int DataSize = 32;

const int BufferSize = 16;

char buffer[BufferSize];

QWaitCondition bufferNotEmpty;

QWaitCondition bufferNotFull;

QMutex mutex;

int used = 0;

class Producor:public QThread

{

public:

    void run();

};

void Producor::run()

{

    qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));

    for(int i = 0; i < DataSize; i++)

    {

        mutex.lock();

        if(used == BufferSize)

            bufferNotFull.wait(&mutex);

        mutex.unlock();

        buffer[i%BufferSize] = used;

        mutex.lock();

        used++;

        bufferNotEmpty.wakeAll();

        mutex.unlock();

    }

}

class Consumer:public QThread

{

public:

    void run();

};

void Consumer::run()

{

    for(int i = 0; i < DataSize; i++)

    {

        mutex.lock();

        if(used == 0)

            bufferNotEmpty.wait(&mutex);

        mutex.unlock();

        fprintf(stderr, "%d\n", buffer[i%BufferSize]);

        mutex.lock();

        used--;

        bufferNotFull.wakeAll();

        mutex.unlock();

    }

    fprintf(stderr, "%c", "\n");

}

int main(int argc, char *argv[])

{

    QCoreApplication a(argc, argv);

    Producor productor;

    Consumer consumer;

    productor.start();

    consumer.start();

    productor.wait();

    consumer.wait();

    return a.exec();

}

8、高级事件队列

QT事件系统对进程间通讯很重要,每一个进程能够有本身的事件循环,要在另一个线程中调用一个槽函数(或任何invokable方法),须要将调用槽函数放置在目标线程的事件循环中,让目标线程在槽函数开始运行以前,先完成本身的当前任务,而原来的线程继续并行运行。

要在一个事件循环中执行调用槽函数,须要一个queued信号槽链接。每当信号发出时,信号的参数将被事件系统记录。信号接收者存活的线程将运行槽函数。另外,不使用信号,调用QMetaObject::invokeMethod()也能够达到相同的效果。在这两种状况下,必须使用queued链接,由于direct链接绕过了事件系统,而且当即在当前线程中运行此方法。

    当线程同步使用事件系统时,没有死锁风险。然而,事件系统不执行互斥。若是调用方法访问共享数据,仍然须要使用QMutex来保护。

若是只使用信号槽,而且线程间没有共享变量,那么,多线程程序能够彻底没有低级原语。

5、可重入与线程安全

可重入reentrant与线程安全thread-safe被用来讲明一个函数如何用于多线程程序。

一个线程安全的函数能够同时被多个线程调用,甚至调用者会使用共享数据也没有问题,由于对共享数据的访问是串行的。一个可重入函数也能够同时被多个线程调用,可是每一个调用者只能使用本身的数据。所以,一个线程安全的函数老是可重入的,但一个可重入的函数并不必定是线程安全的。

    一个可重入的类,指的是类的成员函数能够被多个线程安全地调用,只要每一个线程使用类的不一样的对象。而一个线程安全的类,指的是类的成员函数可以被多线程安全地调用,即便全部的线程都使用类的同一个实例。

1、可重入

    大多数C++类是可重入的,由于它们典型地仅仅引用成员数据。任何线程能够访问可重入类实例的成员函数,只要同一时间没有其余线程调用这个实例的成员函数。

class Counter
{
  public:
      Counter() {n=0;}
      void increment() {++n;}
      void decrement() {--n;}
      int value() const {return n;}
 private:
      int n;
};

    Counter类是可重入的,但却不是线程安全的。假如多个线程都试图修改数据成员n,结果未定义。

    大多数Qt类是可重入,非线程安全的。有一些类与函数是线程安全的,主要是线程相关的类,如QMutex,QCoreApplication::postEvent()。

2、线程安全

    全部的GUI类(如QWidget及其子类),操做系统核心类(如QProcess)和网络类都不是线程安全的。

class Counter
 {
 public:
     Counter() { n = 0; }

void increment() { QMutexLocker locker(&mutex); ++n; }
     void decrement() { QMutexLocker locker(&mutex); --n; }
     int value() const { QMutexLocker locker(&mutex); return n; }

private:
     mutable QMutex mutex;
     int n;
 };

 Counter类是可重入和线程安全的。QMutexLocker类在构造函数中自动对mutex进行加锁,在析构函数中进行解锁。mutex使用了mutable关键字来修饰,由于在value()函数中对mutex进行加锁与解锁操做,而value()是一个const函数。

6、线程与信号槽

1、线程的依附性

    线程的依附性是对象与线程的关系。默认状况下,对象依附于自身被建立的线程。

    对象的依附性与槽函数执行的关系,默认状况下,槽函数在其所依附的线程中被调用执行。

    修改对象的依附性的方法:QObject::moveToThread函数用于改变对象的线程依附性,使得对象的槽函数在依附的线程中被调用执行。

2QObject线程

QThread类具备发送信号和定义槽函数的能力。QThread主要信号以下:

void started();线程开始运行时发送信号

void finished();线程完成运行时发送信号

void terminated();线程被异常终止时发送信号

    QThread继承自QObject,发射信号以指示线程执行开始与结束,并提供了许多槽函数。QObjects能够用于多线程,发射信号以在其它线程中调用槽函数,而且向“存活”于其它线程中的对象发送事件
QObject的可重入性

    QObject是可重入的,QObject的大多数非GUI子类 QTimerQTcpSocketQUdpSocketQHttpQFtpQProcess也是可重入的,在多个线程中同时使用这些类是可能的。可重入的类被设计成在一个单线程中建立与使用,在一个线程中建立一个对象而在另外一个线程中调用该对象的函数,不保证能行得通。有三种约束须要注意:

    A、一个QObject类型的孩子必须老是被建立在它的父亲所被建立的线程中。这意味着,除了别的之外,永远不要把QThread对象(this)做为该线程中建立的一个对象的父亲(由于QThread对象自身被建立在另一个线程中)。

    B、事件驱动的对象可能只能被用在一个单线程中。特别适用于计时器机制(timer mechanism)和网络模块。例如:不能在不属于这个对象的线程中启动一个定时器或链接一个socket,必须保证在删除QThread以前删除全部建立在这个线程中的对象。在run()函数的实现中,经过在栈中建立这些对象,能够轻松地作到这一点。

    C、虽然QObject是可重入的,但GUI类,尤为是QWidget及其全部子类都不是可重入的,只能被用在GUI线程中。QCoreApplication::exec()必须也从GUI线程被调用。

    在实践中,只能在主线程而非其它线程中使用GUI的类,能够很轻易地被解决:将耗时操做放在一个单独的工做线程中,当工做线程结束后在GUI线程中由屏幕显示结果。

    通常来讲,在QApplication前建立QObject是不行的,会致使奇怪的崩溃或退出,取决于平台。所以,不支持QObject的静态实例。一个单线程或多线程的应用程序应该先建立QApplication,并最后销毁QObject。

3、线程的事件循环

    每一个线程都有本身的事件循环。主线程经过QCoreApplication::exec()来启动本身的事件循环, 但对话框的GUI应用程序,有些时候用QDialog::exec(),其它线程能够用QThread::exec()来启动事件循环。就像 QCoreApplication,QThread提供一个exit(int)函数和quit()槽函数

    线程中的事件循环使得线程能够利用一些非GUI的、要求有事件循环存在的Qt类(例如:QTimer、QTcpSocket、和QProcess),使得链接一些线程的信号到一个特定线程的槽函数成为可能。

    

    一个QObject实例被称为存活于它所被建立的线程中。关于这个对象的事件被分发到该线程的事件循环中。能够用QObject::thread()方法获取一个QObject所处的线程。

    QObject::moveToThread()函数改变一个对象和及其子对象的线程所属性。(若是对象有父对象的话,对象不能被移动到其它线程中)。

从另外一个线程(不是QObject对象所属的线程)对该QObject对象调用delete方法是不安全的,除非能保证该对象在那个时刻不处理事件,使用QObejct::deleteLater()更好。一个DeferredDelete类型的事件将被提交(posted),而该对象的线程的 件循环最终会处理这个事件。默认状况下,拥有一个QObject的线程就是建立QObject的线程,而不是 QObject::moveToThread()被调用后的。

    若是没有事件循环运行,事件将不会传递给对象。例如:在一个线程中建立了一个QTimer对象,但从没有调用exec(),那么,QTimer就永远不会发射timeout()信号,即便调用deleteLater()也不行。(这些限制也一样适用于主线程)。

    利用线程安全的方法QCoreApplication::postEvent(),能够在任什么时候刻给任何线程中的任何对象发送事件,事件将自动被分发到该对象所被建立的线程事件循环中。

    全部的线程都支持事件过滤器,而限制是监控对象必须和被监控对象存在于相同的线程中。QCoreApplication::sendEvent()(不一样于postEvent())只能将事件分发到和该函数调用者相同的线程中的对象。

4、其余线程访问QObject子类

    QObject及其全部子类都不是线程安全的。这包含了整个事件交付系统。重要的是,切记事件循环可能正在向你的QObject子类发送事件,当你从另外一个线程访问该对象时。

    若是你正在调用一个QObject子类的函数,而该子类对象并不存活于当前线程中,而且该对象是能够接收事件的,那么你必须用一个mutex保护对该QObject子类的内部数据的全部访问,不然,就有可能发生崩溃和非预期的行为。

    同其它对象同样,QThread对象存活于该对象被建立的线程中 – 而并不是是在QThread::run()被调用时所在的线程。通常来讲,在QThread子类中提供槽函数是不安全的,除非用一个mutex保护成员变量。

    另外一方面,能够在QThread::run()的实现中安全地发射信号,由于信号发射是线程安全的。

5、跨线程的信号槽

    线程的信号槽机制须要开启线程的事件循环机制,即调用QThread::exec()函数开启线程的事件循环。

Qt信号-槽链接函数原型以下:

bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection )

Qt支持5种链接方式

A、Qt::DirectConnection(直连方式)(信号与槽函数关系相似于函数调用,同步执行)

    当信号发出后,相应的槽函数将当即被调用。emit语句后的代码将在全部槽函数执行完毕后被执行。

    当信号发射时,槽函数将直接被调用。

    不管槽函数所属对象在哪一个线程,槽函数都在发射信号的线程内执行。

B、Qt::QueuedConnection(队列方式)(此时信号被塞到事件队列里,信号与槽函数关系相似于消息通讯,异步执行)

    当信号发出后,排队到信号队列中,需等到接收对象所属线程的事件循环取得控制权时才取得该信号,调用相应的槽函数。emit语句后的代码将在发出信号后当即被执行,无需等待槽函数执行完毕。

    当控制权回到接收者所依附线程的事件循环时,槽函数被调用。

    槽函数在接收者所依附线程执行。

C、Qt::AutoConnection(自动方式)

     Qt的默认链接方式,若是信号的发出和接收信号的对象同属一个线程,那个工做方式与直连方式相同;不然工做方式与队列方式相同。

若是信号在接收者所依附的线程内发射,则等同于直接链接

若是发射信号的线程和接受者所依附的线程不一样,则等同于队列链接

D、Qt::BlockingQueuedConnection(信号和槽必须在不一样的线程中,不然就产生死锁)

    槽函数的调用情形和Queued Connection相同,不一样的是当前的线程会阻塞住,直到槽函数返回。

E、Qt::UniqueConnection

    与默认工做方式相同,只是不能重复链接相同的信号和槽,由于若是重复链接就会致使一个信号发出,对应槽函数就会执行屡次。

    QThread是用来管理线程的,QThread对象所依附的线程和所管理的线程并非同一个概念。QThread所依附的线程,就是建立QThread对象的线程,QThread 所管理的线程,就是run启动的线程,也就是新建线程。QThread对象依附在主线程中,QThread对象slot函数会在主线程中执行,而不是次线程。除非QThread对象依附到次线程中(经过movetoThread)

工程实践中,为了不冻结主线程的事件循环(即避免所以而冻结了应用的UI),全部的计算工做是在一个单独的工做线程中完成的,工做线程结束时发射一个信号,经过信号的参数将工做线程的状态发送到GUI线程的槽函数中更新GUI组件状态。

7、线程的设计

一、线程的生命周期

若是线程的正处于执行过程当中时,线程对象被销毁时,程序将会出错。

工程实践中线程对象的生命期必须大于线程的生命期。

二、同步线程类设计

线程对象主动等待线程生命期结束后才销毁,线程对象销毁时确保线程执行结束,支持在栈或堆上建立线程对象。

在线程类的析构函数中先调用wait函数,强制等待线程执行结束。

使用场合:适用于线程生命期较短的场合

#ifndef SYNCTHREAD_H

#define SYNCTHREAD_H

 

#include <QThread>

 

class SyncThread : public QThread

{

  Q_OBJECT

protected:

  void run()

  {

    

  }

public:

  explicit SyncThread(QObject* parent = 0):QThread(parent)

  {

    

  }

  ~SyncThread()

  {

    wait();

  }

};

 

#endif // SYNCTHREAD_H

3、异步线程类设计

线程生命期结束时通知线程对象销毁。

只能在堆空间建立线程对象,线程对象不能被外界主动销毁。

run函数中最后调用deleteLater()函数。

线程函数主动申请销毁线程对象。

使用场合:

线程生命期不可控,须要长时间运行于后台的线程。

#ifndef ASYNCTHREAD_H

#define ASYNCTHREAD_H

 

#include <QThread>

 

class AsyncThread : public QThread

{

  Q_OBJECT

protected:

  void run()

  {

 

    deleteLater();

  }

  explicit AsyncThread(QObject* parent = 0):QThread(parent)

  {

 

  }

  ~AsyncThread()

  {

 

  }

public:

  static AsyncThread* newThread(QObject* parent = 0)

  {

    return new AsyncThread(parent);

  }

};

 

#endif // ASYNCTHREAD_H

8、线程的使用方式

1、子类化QThread

QThread的两种使用方法:

1)不使用事件循环

 A、子类化 QThread

    B、重写run函数,run函数内有一个 while 或 for 的死循环

    C、设置一个标记为来控制死循环的退出。

    适用于后台执行长时间的耗时操做,如文件复制、网络数据读取。

2)使用事件循环。

    A、子类化 QThread

    B、重写run 使其调用 QThread::exec() ,开启线程的事件循环

C、为子类定义信号和槽,因为槽函数并不会在新开的 Thread 运行,在构造函数中调用 moveToThread(this)

适用于事务性操做,如文件读写、数据库读写。

2Worker-Object

    在Qt4.4以前,run 是纯虚函数,必须子类化QThread来实现run函数。
    而从Qt4.4开始,QThread再也不支持抽象类,run 默认调用 QThread::exec() ,不须要子类化 QThread,只须要子类化一个 QObject

    经过继承的方式实现多线程已经没有任何意义,QThread是操做系统线程的接口或控制点,用于充当线程操做的集合。

    使用Worker-Object经过QObject::moveToThread将它们移动到线程中。

    指定一个线程对象的线程入口函数的方法:

A、在类中定义一个槽函数void tmain()做为线程入口函数

B、在类中定义一个QThread成员对象m_thread

C、改变当前对象的线程依附性到m_thread

D、链接m_thread的started()信号到tmain槽函数。

 

#ifndef WORKER_H

#define WORKER_H

 

#include <QObject>

#include <QThread>

#include <QDebug>

 

class Worker : public QObject

{

  Q_OBJECT

  QThread m_thread;

protected slots:

  void tmain()

  {

    qDebug() << "void tmain()";

  }

public:

  explicit Worker(QObject* parent = 0):QObject(parent)

  {

    moveToThread(&m_thread);

    connect(&m_thread, SIGNAL(started()), this, SLOT(tmain()));

  }

  void start()

  {

    m_thread.start();

  }

  void terminate()

  {

    m_thread.terminate();

  }

 

  void exit(int c)

  {

    m_thread.exit(c);

  }

  ~Worker()

  {

    m_thread.wait();

  }

};

 

#endif // WORKER_H

9、多线程与GUI组件的通讯

1、多线程与GUI组件通讯基础

    GUI系统的设计原则:

    全部界面组件的建立只能在GUI线程(主线程)中完成。子线程与界面组件的通讯有两种方式:

    A、信号槽方式

    B、发送自定事件方式

2、信号槽方式

使用信号槽解决多线程与界面组件的通讯的方案:

A、在子线程中定义界面组件的更新信号

B、在主窗口类中定义更新界面组件的槽函数

C、使用异步方式链接更新信号到槽函数

子线程经过发送信号的方式更新界面组件,全部的界面组件对象只能依附于GUI线程(主线程)。

子线程更新界面状态的本质是子线程发送信号通知主线程界面更新请求,主线程根据具体信号以及信号参数对界面组件进行修改。

使用信号槽在子线程中更新主界面中进度条的进度显示信息。

工做线程类:

#ifndef WORKTHREAD_H

#define WORKTHREAD_H

#include <QThread>

 

class WorkThread : public QThread

{

  Q_OBJECT

signals:

  void signalProgressValue(int value);

protected:

  void run()

  {

    work();

    exec();

  }

 

public:

  WorkThread()

  {

    m_stop = false;

    moveToThread(this);

  }

  void work()

  {

    for(int i = 0; i < 11; i++)

    {

        emit signalProgressValue(i*10);

        sleep(1);

    }

  }

};

 

#endif // WORKTHREAD_H

主界面类:

#ifndef WIDGET_H

#define WIDGET_H

 

#include <QWidget>

#include <QProgressBar>

#include "WorkThread.h"

 

class Widget : public QWidget

{

  Q_OBJECT

  QProgressBar* m_progress;//进度条

  WorkThread* m_thread;//工做线程

public:

  Widget(QWidget *parent = 0):QWidget(parent)

  {

    m_progress = new QProgressBar(this);

    m_progress->move(10, 10);

    m_progress->setMinimum(0);

    m_progress->setMaximum(100);

    m_progress->setTextVisible(true);

    m_progress->resize(100, 30);

    m_thread = new WorkThread();

    m_thread->start();

    connect(m_thread, SIGNAL(finished()), m_thread, SLOT(deleteLater()));

    //链接工做线程的信号到界面的槽函数

    connect(m_thread, SIGNAL(signalProgressValue(int)), this, SLOT(onProgress(int)));

  }

  ~Widget()

  {

  }

protected slots:

  void onProgress(int value)

  {

    m_progress->setValue(value);

  }

};

 

#endif // WIDGET_H

Main函数:

#include "Widget.h"

#include <QApplication>

 

int main(int argc, char *argv[])

{

  QApplication a(argc, argv);

  Widget w;

  w.show();

 

  return a.exec();

}

3、发送自定义事件方式

    A、自定义事件用于描述界面更新细节

    B、在主窗口类中重写事件处理函数event

    C、使用postEvent函数(异步方式)发送自定义事件类对象

    子线程指定接收消息的对象为主窗口对象,在event事件处理函数更新界面状态

    事件对象在主线程中被处理,event函数在主线程中调用。

    发送的事件对象必须在堆空间建立

    子线程建立时必须附带目标对象的地址信息

自定义事件类:

#ifndef PROGRESSEVENT_H

#define PROGRESSEVENT_H

#include <QEvent>

 

class ProgressEvent : public QEvent

{

  int m_progress;

public:

  const static Type TYPE = static_cast<Type>(QEvent::User + 0xFF);

  ProgressEvent(int progress = 0):QEvent(TYPE)

  {

    m_progress = progress;

  }

  int progress()const

  {

    return m_progress;

  }

};

 

#endif // PROGRESSEVENT_H

 

自定义线程类:

#ifndef WORKTHREAD_H

#define WORKTHREAD_H

#include <QThread>

#include <QApplication>

#include <ProgressEvent.h>

 

class WorkThread : public QThread

{

  Q_OBJECT

protected:

  volatile bool m_stop;

  void run()

  {

    work();

    exec();

  }

 

public:

  WorkThread()

  {

    m_stop = false;

  }

  void stop()

  {

    m_stop = true;

  }

  void work()

  {

    for(int i = 0; i < 11; i++)

    {

        QApplication::postEvent(parent(), new ProgressEvent(i*10));

        sleep(1);

    }

  }

};

 

#endif // WORKTHREAD_H

自定义界面类:

#ifndef WIDGETUI_H

#define WIDGETUI_H

 

#include <QWidget>

#include <QProgressBar>

#include "WorkThread.h"

#include "ProgressEvent.h"

 

class WidgetUI : public QWidget

{

  Q_OBJECT

  QProgressBar* m_progress;//进度条

  WorkThread* m_thread;//工做线程

public:

  WidgetUI(QWidget *parent = 0):QWidget(parent)

  {

    m_progress = new QProgressBar(this);

    m_progress->move(10, 10);

    m_progress->setMinimum(0);

    m_progress->setMaximum(100);

    m_progress->setTextVisible(true);

    m_progress->resize(100, 30);

    m_thread = new WorkThread();

    m_thread->setParent(this);

    m_thread->start();

  }

  ~WidgetUI()

  {

    m_thread->quit();

  }

protected:

  bool event(QEvent *event)

  {

    bool ret = true;

    if(event->type() == ProgressEvent::TYPE)

    {

        ProgressEvent* evt = dynamic_cast<ProgressEvent*>(event);

        if(evt != NULL)

        {

            //设置进度条的进度为事件参数的值

            m_progress->setValue(evt->progress());

        }

    }

    else

    {

        ret = QWidget::event(event);

    }

    return ret;

  }

};

 

#endif // WIDGETUI_H

Main函数:

#include "WidgetUI.h"

#include <QApplication>

 

int main(int argc, char *argv[])

{

  QApplication a(argc, argv);

  WidgetUI w;

  w.show();

 

  return a.exec();

}

相关文章
相关标签/搜索