QT多线程(三)线程互斥与同步

前言


线程之间存在着相互制约的关系:

  1. 互斥关系,如线程争夺I/O设备而导致一方必须等待一方使用结束后方可使用

  2. 同步关系,完成同一任务的线程之间,需要协调它们的工作而相互等待、交互

 

临界区


先看这个类:

class Key
{
public:
	Key() { key = 0 ; }
	int creatKey() { ++key; return key; }
	int value() const { return key ; }

private:
	int key;
};

在多线程环境下,这个类是不安全的,存在多个线程同时修改成员key的情况,其结果是不可预知的,虽然++k只是一条语句,但它并不是原子操作,编译后将会展开成三条指令:将变量载入寄存器、将 寄存器中的值加1、将寄存器中的值写回主存。

为了保证类Key 在多线程环境下正确执行,上述三条机器指令必须串行执行且不允许被打断,并且每次只有一个线程操作。

一次只允许一个线程使用的资源成为临界资源,临界资源可以是一块内存、一个数据结构、一个文件等,必须互斥执行的代码段被称为“临界区”。

QT为实现线程之间的互斥与同步提供了以下几个类:QMutex、QMutexLocker、QReadWriteLocker、QReadLocker、QWriteLocker、QSemaphore、QWaitCondition。

 

 

QMutex / QMutexLocker


QMutex可以保护临界区一次只被一个线程执行。如果一个线程正在使用临界资源,其它线程则会被阻塞,直到临界资源被释放。

使用QMtuex操作Key类:

class Key
{
public:
    Key() { key = 0 ; }
    int creatKey() 
    { mutex.lock();  ++key; return key; mutex.unlock(); }
        
    int value() const 
    { mutex.lock();  return key ;  mutex.unlock(); }
​
private:
    int key;
    QMutex mutex;
};

虽然是用了mutex进行了互斥操作,但unlock在return语句之后,导致unlock()无法执行。

QMutex还提供了try()函数,如果互斥量已经被锁定,就立即返回,不会等待解锁。

 

QMutexLocker可以解决此问题:

class Key
{
public:
    Key() { key = 0 ; }
    int creatKey() 
    { QMutexLocker locker(&mutex);  ++key; return key; }
        
    int value() const 
    { QMutexLocker locker(&mutex);  return key ; }
​
private:
    int key;
    QMutex mutex;
};

locker作为局部变量,在退出作用域后会自动调用析构函数来unlock对互斥量mutex解锁。

 

QSemaphore


Qsemaphore是信号量,可以理解为对互斥量的功能扩展,互斥量只能锁定一次释放一次,而信号量可以释放多次。acquire(n=1)函数可以获取n个资源,当没有足够的资源时,线程会被阻塞。直到有n个资源可被利用。

可以将acquire(n=1)理解为将 -n, release(n=1)理解为 +n。

tryAcqire(n)函数在没有n个资源的情况下会立即返回。

下面这个例子,freed表示有多少个单元可以写入数据,初始化全部可写。used表示当前有多少个单元可以读入数据,初始化为0个。

producer线程和customer线程同时运行,但开始时,customer线程里的used.acquire()想要获得一个可读单元,而目前没有可读单元,所以customer线程会被阻塞,直到produce线程释放资源。

#include <QtCore/QCoreApplication>
#include <QSemaphore>
#include <QThread>
#include <iostream>
​
const int DataSize = 100;
const int BufferSize = 10;
char buffer[BufferSize];
​
QSemaphore freed(BufferSize); //有BufferSize个可写单元
QSemaphore used(0); //有0个可读单元
​
class Producer : public QThread
{
protected:
    void run()
    {
        for (int i = 0; i < DataSize; ++i)
        {
            freed.acquire(); //freed -= 1 用掉一个可写单元
            std::cout<<"P";
            used.release(); //used += 1 释放一个可读单元
        }
    }
};
​
class Consumer : public QThread
{
protected:
    void run()
    {
        sleep(1); 
        for (int i = 0; i < DataSize; ++i)
        {
            used.acquire(); //用掉一个可读单元
            std::cout<<"C";
            freed.release(); //释放一个可写单元
        }
        std::cerr<<std::endl;
    }
};
​
int main(int argc, char *argv[])
{
    Producer producer;
    Consumer consumer;
    producer.start();
    consumer.start();
    producer.wait();
    consumer.wait();
    system("pause");
    return 0;
}

 

 

QwaitCondition


 

QwaitCondition类有两个主要的函数,wait() 和 wakeAll()。

wait()函数有两个参数,第一个为互斥量,第二个为等待时间,默认无限长。

调用wait()的线程使互斥量解锁,使自己被阻塞直到其它线程调用wakeAll()或者wakeOne() (返回true),或者超过等待时间(返回false)。重新将互斥量置为锁定状态。

#include <QtCore>
#include <stdio.h>
#include <stdlib.h>
​
const int DataSize = 10000;
const int BufferSize = 8192;
int buffer[BufferSize];
​
QWaitCondition bufferEmpty; //缓冲区有空位条件
QWaitCondition bufferFull;   //缓冲区有可用数据
QMutex mutex;
int numUsedBytes = 0;  //可用字节
int rIndex=0;          //当前读取缓冲区位置
​
class Producer : public QThread
{
public:
    void run()
    {
        for (int i = 0; i < DataSize; ++i)
        {
            mutex.lock();
            if (numUsedBytes == BufferSize) //如果已经写满
                bufferEmpty.wait(&mutex); //解锁并等待缓冲区有空位条件bufferEmpty,一旦等到了,重新上锁
            //         mutex.unlock();
            buffer[i % BufferSize] = numUsedBytes; //写数据
            //         mutex.lock();
            ++numUsedBytes; //可用字节+1
            bufferFull.wakeAll(); //唤醒缓冲区有可用数据条件
            mutex.unlock(); //
        }
    }
};
​
​
class Consumer : public QThread
{
public:
    void run()
    {
        forever 
        {
            mutex.lock();
            if (numUsedBytes == 0) //如果没有可用数据
                bufferFull.wait(&mutex); //解锁等待有可用数据条件bufferFull,等到了,重新上锁
            //         mutex.unlock();
            printf("%ul::[%d]=%d->%d \n", currentThreadId (),rIndex,buffer[rIndex],numUsedBytes);
            //         mutex.lock();
            rIndex = (++rIndex)%BufferSize;
            --numUsedBytes; //用掉一个字节
            bufferEmpty.wakeAll(); //唤醒有空位条件
            mutex.unlock();
        }
        printf("\n");
    }
};
​
​
int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
​
    for (int i = 0; i < BufferSize; ++i) 
            buffer[i] = -1;             
    
    Producer producer;
    Consumer consumerA;
    Consumer consumerB;
​
    producer.start();
    consumerA.start();
    consumerB.start();
    producer.wait();
    consumerA.wait();
    consumerB.wait();
    return 0;
}

 

 

QReadWriteLock


Qt中的QReadWriteLock类为我们提供了读写锁的功能。读写锁是用来保护可以被读访问和写访问的资源的一种同步工具。

可以让多个线程同时的对资源进行读访问,但只要有一个线程要对资源进行写访问时,所有其他的线程必须等待,直到写访问完成。对于这种情况,读写锁是非常有用的。

#include <QtCore/QCoreApplication>
#include <QReadWriteLock>
#include <QThread>
#include <iostream>
​
QReadWriteLock rwLock;  
​
class Write : public QThread
{
public:
    void run()
    {
        forever
        {
            rwLock.lockForWrite();
            std::cout << "write" << std::endl;
            sleep(1);
            rwLock.unlock();
        }
    }
};
​
class Read : public QThread
{
    void run()
    {
        forever
        {
            rwLock.lockForRead();
            std::cout << "read" << std::endl;
            rwLock.unlock();
        }
    }
};
int main(int argc, char *argv[])
{
    Write write;
    Read read[10];
    
    write.start();
    for(int i = 0; i < 10; i++) 
        read[i].start();
​
    write.wait();
    for(int i = 0; i < 10; i++) 
        read[i].wait();
    return 0;
}

 

QT还提供了QReadLocker和QWriteLocker两个类,这两个类在构造函数中接收一个QReadWriteLock,构造函数中对其锁定,析构函数进行解锁。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章