概述
前言
线程之间存在着相互制约的关系:
-
互斥关系,如线程争夺I/O设备而导致一方必须等待一方使用结束后方可使用
-
同步关系,完成同一任务的线程之间,需要协调它们的工作而相互等待、交互
临界区
先看这个类:
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,构造函数中对其锁定,析构函数进行解锁。
最后
以上就是结实西牛为你收集整理的QT多线程(三)线程互斥与同步的全部内容,希望文章能够帮你解决QT多线程(三)线程互斥与同步所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复