天天看点

Qt之多线程(二)

1、QMutex

       QMutex类提供的是线程之间的访问顺序化

       QMutex的目的是保护一个对象、数据结果或者代码实同一时间只有一个线程访问它们。(在Java术语中,它和同步关键字“synchronized”很相似)。一般最好使用QMutexLocker,它能确保锁定和解锁保持一致。

例如,这里有一个方法打印给用户两条消息:

  void someMethod()

  {

     qDebug()<<"Hello";

     qDebug()<<"World";

  }

如果同时在两个线程中调用这个方法,结果的顺序将是:

  Hello

  Hello

  World

  World

如果你使用了一个互斥量:

  QMutex mutex;

  void someMethod()

  {

     mutex.lock();

     qDebug()<<"Hello";

     qDebug<<"World";

     mutex.unlock();

  }

用Java的术语,这段代码应该是:

  void someMethod()

  {

     synchronized {

       qDebug()<<"Hello";

       qDebug()<<"World";

     }

  }

成员类型:

       enum QMutex::RecursionMode

             QMutex::Recursive                1       在这种模式下,一个线程能多次锁定,并且锁定不会被解除直到被相应的解锁。

             QMutex::NonRecursive        0        在这种模式下,一个下称仅能锁定一次

成员函数:

QMutex::QMutex( Recursion mode = NonRecursive)

               构造一个新的互斥量。这个互斥量是在没有锁定的状态下创建的。如果recursive为 Recursive,就构造一个递归互斥量,如果recursive 为NonRecursive(默认值),就构造一个普通互斥量。对于一个递归互斥量,一个线程可以锁定一个互斥量多次并且只有在相同数量的unlock ()调用之后,它才会被解锁

QMutex::~QMutex() 

              析构函数,销毁这个互斥量。

void lock()

              锁定互斥量,如果其他线程已经锁定互斥量,那么这次调用将阻塞知道那个线程把它解锁

bool tryLock()

              试图锁定互斥量,如果锁被得到,这个函数返回真。如果另一个进程已经锁定了这个互斥量,这个函数返回假,而不是一直等到这个锁可用为止。如果获得锁,这个互斥量必须被使用unlock()解锁,其他线程才能锁定它。

bool tryLock( int timeout)

             试图锁定互斥量。如果锁被得到,这个函数返回真。如果另一个进程已经锁定了这个互斥量,这个函数返回假,而不是一直等到这个锁可用为止。如果其他线程已经锁定这个互斥量,那么这个函数将最多等待timeout毫秒后返回,如果mutex被解锁则返回true,否则返回false.

void unlock()

             解锁这个互斥量。试图对不同的线程中锁定的互斥量进行解锁将会返回一个错误。对一个没有锁定的互斥量进行解锁的结果是将导致未定义的行为(不同的操作系统的线程实现是有很大不同的)

          注:如果使用QMutex.lock()而没有对应的使用QMutex.unlcok()的话就会造成死锁,别的线程永远也得不到接触该mutex锁住的共享资源的机会。尽管可以不使用lock()而使用tryLock(timeout)来避免因为死等而造成的死锁( tryLock(负值)==lock()),但是还是很有可能造成错误。

2、QReadWriteLock

     QReadWriteLock提供了读写锁。

      如果你允许多个线程同时有读权限,但如果一个线程正在对资源使用写权限,那么其他线程必须等到这个线程对资源解锁写权限后才能使用读权限访问,即写线程执行的时候会阻塞所有的读线程,而读线程之间的运行不需要进行同步,  QReadWriterLock 与QMutex相似,除了它对 "read","write"访问进行区别对待。它使得多个读者可以共时访问数据。使用QReadWriteLock而不是QMutex,可以使得多线程程序更具有并发性。解决了mutex只允许某个时刻只允许一个线程对共享资源进行访问。

QReadWriteLock lock;

void ReaderThread::run()

{

...

lock.lockForRead();

read_file();

lock.unlock();

...

}

void WriterThread::run()

{

...

lock.lockForWrite();

write_file();

lock.unlock();

...

}

类成员

enum    RecursionMode { Recursive, NonRecursive }

函数成员:

QReadWriteLock ()

QReadWriteLock ( RecursionMode recursionMode )

~QReadWriteLock ()

void    lockForRead ()

void    lockForWrite ()

bool    tryLockForRead ()

bool    tryLockForRead ( int timeout )

bool    tryLockForWrite ()

bool    tryLockForWrite ( int timeout )

void    unlock ()

3、QReadLocker

        对于QMutex有QMutexLocker来简化使用,而对于QReadWriteLock有 QReadLocker和QWriteLocker

          成员方法:

         QReadLocker ( QReadWriteLock * lock )

         ~QReadLocker ()

         QReadWriteLock *    readWriteLock () const

         void    relock ()

         void    unlock ()

          QReadWriteLock lock;

QByteArray readData()

{

QReadLocker locker(&lock);

...

return data;

}

//****************************************

QReadWriteLock lock;

QByteArray readData()

{

lock.lockForRead();

...

lock.unlock();

return data;

}

4、QWriteLocker

     函数成员:

    QWriteLocker ( QReadWriteLock * lock )

    ~QWriteLocker ()

    QReadWriteLock *    readWriteLock () const

    void    relock ()

    void    unlock ()

QReadWriteLock lock;

void writeData(const QByteArray &data)

{

QWriteLocker locker(&lock);

...

}

//**************************************************

QReadWriteLock lock;

void writeData(const QByteArray &data)

{

lock.lockForWrite();

...

lock.unlock();

}

5、QSemaphore

     QSemaphore类提供了一种通用计数信号。

     QSemaphore是一个顺序化的 互斥量(QMutex) 。一个互斥量仅能被锁定一次,但是 信号量 有可能获得多次。信号量通常用来保护一定数量的相同资源。信号量和互斥量的不同在于,信号量可以在同一时间被多于一个的线程访问。

    例如,假设我们有一个应用程序把数据存储到一个大的树型结构中。应用程序创建了10个线程(通常被称作线程池)来执行树中的搜索。当应用程序搜索树中的一小片数据,它在每个基本节点上使用一个线程来执行搜索。一个信号量就可以用来确保两个线程不必在同一时间内试图对树的同一个分支进行搜索。

    QSemaphore::QSemaphore ( int maxcount ) 创建一个新的信号量,并初始化n个资源给它守护

    信号量支持两种基本操作:

   1) void acquire(int n = 1)   试图获取由信号量看守的n个资源,如果n > avalibable(),这次调用将阻塞直到有那么多资源可用

   2)void release(int n = 1)   释放n个被信号量守护的资源

        这个函数也能被用来创建资源,例如:

        QSemaphore sem(5); // 创建一个守护5个资源的信号量

sem.acquire(5); // 获取已有的5个资源(全部信号量)

sem.release(5); // 释放5个资源

sem.release(10); // 创建10个新的资源

    一个信号量的非计算实例是在餐馆就餐。信号量被初始化为最大值等于餐馆内的椅子数。当人们来了,他们想要一个座位。当座位满了,信号量就被访问,每次一个人。当人们离开了,访问就被释放,允许更多的人进入。如果一个10人的聚会想坐下,但是这里只有9个座位,那么这10个人就必须等,但是一个4人的聚会就可以坐下了(剩下的座位就是5了,那么那10个人就得等更长的时间了)。

    int available() const    返回当前信号量可用资源的数量,这个数字不可能为负数

    bool tryAcquire(int n = 1)   试图获取被当前信号量守护的n个资源,如果available() >=n ,则返回true,否则将立刻返回false且不能获取任何资源。

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

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

sem.tryAcquire(3); // sem.available() == 2, returns true

    bool tryAcquire(int n, int timeout)   试图获取被当前信号量守护的n个资源,如果available() >=n ,则返回true,否则将等待timeout毫秒,若期间达到n个可用资源就立即返回true,若timeout毫秒后还没达到返回false,且不获取任何资源。

    注意:如果timeout为负数,则相当于调用tryAcquire(),即一直等待,直到有那么多可用资源.

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

sem.tryAcquire(250, 1000); // sem.available() == 5, 等待1000毫秒,返回false

sem.tryAcquire(3, 30000); // sem.available() == 2, 不等待,直接返回true

6、WaitCondition

    QWaitCondition类是线程之间允许等待/唤醒的条件。

    QWaitConditions允许一个线程告诉其它线程某种条件已经满足,一个或多个线程可以等待一个由wakeOne()或wakeAll()设定的条件QWaitCondition。使用wakeOne()会唤醒一种随机选择的事件或者wakeAll()会把它们全部唤醒。比如,假定每次用户按下一个键,我们有三个任务要同时执行,每个任务都可以放到一个线程中,每个线程的run()都应该是这样:

     QWaitCondition key_pressed;

while (true) {

key_pressed.wait(); // 这是一个QWaitCondition全局变量

// 键被按下,做一些有趣的事

do_something();

第四个线程回去读键按下并且每当它接收到一个的时候唤醒其它三个线程,就像这样:

     QWaitCondition key_pressed;

while (true) {

getchar();

// 在key_pressed中导致引起任何一个线程。wait()将会从这个方法中返回并继续执行

key_pressed.wakeAll();

注意这三个线程被唤醒的顺序是未定义的,并且当键被按下时,这些线程中的一个或多个还在do_something(),它们将不会被唤醒(因为它们现在没有等待条件变量)并且这个任务也就不会针对这次按键执行操作。这种情况是可以避免得,比如,就像下面这样做:

      QMutex mymutex;

QWaitCondition key_pressed;

int mycount=0;

// 工人线程代码

while (true) {

key_pressed.wait(); // 这是一个QWaitCondition全局变量

mymutex.lock();

mycount++;

mymutex.unlock();

do_something();

mymutex.lock();

mycount--;

mymutex.unlock();

}

// 读取按键线程代码

while (true) {

getchar();

mymutex.lock();

// 睡眠,直到没有忙碌的工作线程才醒来。

while( count > 0 ) {

mymutex.unlock();

sleep( 1 );

mymutex.lock();

}

mymutex.unlock();

key_pressed.wakeAll();

}     

   互斥量是必须的,因为两个线程试图同时对同一个变量进行修改的结果是不可预知的。

   QWaitCondition () 创建一个新的等待条件对象

   ~QWaitCondition ()  析够函数

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

         释放锁定的mutex并且在线程事件对象上等待。mutex必须由调用线程初始锁定的。如果mutex没有在锁定状态,这个函数立即返回。如果mutex是一个递归互斥量,这个函数立即返回。mutex将被解锁,并且调用线程将会阻塞,直到下列条件之一满足时才醒来:

            * 另一个线程使用wakeOne()或wakeAll()传输信号给它。在这种情况下,这个函数将返回真。

            * time毫秒过去了。如果time为ULONG_MAX(默认值),那么这个等待将永远不会超时(这个事件必须被传输)。如果等待的事件超时,这个函数将会返回假。

        mutex 将以同样的锁定状态返回。这个函数提供的是允许从锁定状态到等待状态的原子转换。

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

           释放锁定的readWriteLock并且在线程事件对象上等待。readWriteLock必须由调用线程初始锁定的。如果readWriteLock没有在锁定状态,这个函数立即返回。如果readWriteLock是一个递归互斥量,这个函数将不会正确的释放锁。readWriteLock将被解锁,并且调用线程将会阻塞,直到下列条件之一满足时才醒来:

            * 另一个线程使用wakeOne()或wakeAll()传输信号给它。在这种情况下,这个函数将返回真。

            * time毫秒过去了。如果time为ULONG_MAX(默认值),那么这个等待将永远不会超时(这个事件必须被传输)。如果等待的事件超时,这个函数将会返回假。

        readWriteLock 将以同样的锁定状态返回。这个函数提供的是允许从锁定状态到等待状态的原子转换。   

   void    wakeAll ()   这将会唤醒所有等待QWaitCondition的线程。这些线程被唤醒的顺序依赖于操组系统的调度策略,并且不能被控制或预知。

   void    wakeOne ()    这将会唤醒所有等待QWaitCondition的线程中的一个线程。这个被唤醒的线程依赖于操组系统的调度策略,并且不能被控制或预知。

   7、QThreadStorage

        QThreadStorage是一个模板类提供逐线程数据存储.

        由于编译器的限制,QThreadStorage仅能存储指针。

        setLocalData()函数保存了一个单一线程值,以供线程调用.这个值能使用localData()函数访问.

        QThreadStorage拥有数据(必须是被新创建在堆上的)和当线程退出就删除数据的所有权。

        hasLocalData()函数允许程序员确定数据是否先前已经设置了使用setLocalData()函数。这对用于延迟初始化有用。

        成员函数:

        QThreadStorage ()

        ~QThreadStorage ()

        bool    hasLocalData () const

        T &    localData ()         T    localData () const

        void    setLocalData ( T data )

        例如,下面的代码使用QThreadStorage去为每个线程存储一个单缓存,有 cacheObject()和removeFromCache()函数。这个cache当线程退出时就自动删除.

        QThreadStorage<QCache<QString, SomeClass> *> caches;

void cacheObject(const QString &key, SomeClass *object)

{

if (!caches.hasLocalData())

caches.setLocalData(new QCache<QString, SomeClass>);

caches.localData()->insert(key, object);

}

void removeFromCache(const QString &key)

{

if (!caches.hasLocalData())

return;

caches.localData()->remove(key);

}

      注意:

  • As noted above, QThreadStorage can only store pointers due to compiler limitations.
  • The QThreadStorage destructor does not delete per-thread data. QThreadStorage only deletes per-thread data when the thread exits or when  setLocalData () is called multiple times.
  • QThreadStorage can be used to store data for the  main()   thread. QThreadStorage deletes all data set for the  main()   thread whenQApplication   is destroyed, regardless of whether or not the  main()   thread has actually finished.

非线程安全类

       这个类不是线程安全的,因为假如多个线程都试图修改数据成员 n,结果未定义。这是因为c++中的++和--操作符不是原子操作。实际上,它们会被扩展为三个机器指令:

           1,把变量值装入寄存器

           2,增加或减少寄存器中的值

           3,把寄存器中的值写回内存

           假如线程A与B同时装载变量的旧值,在寄存器中增值,回写。他们写操作重叠了,导致变量值仅增加了一次。很明显,访问应该串行化:A执行123步骤时不应被打断。

     class Counter

{

public:

Counter() {n=0;}

void increment() {++n;}

void decrement() {--n;}

int value() const {return n;}

private:

int n;

}; 

线程安全类:

     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;

}; 

     到此,与线程相关的类已经基本学完。下一步学习文件操作,然后自己做个小东东.