QT知识点总结——QMutex、QSemaphore与QReadWriteLock
本文介绍了QT中的三个相关知识点及其应用:
- 互斥量QMutex、QMutexLocker
- 信号量QSemaphore
- 读写锁QReadWriteLock、QReadLocker、QWriteLocker
1. QMutex和QMutexLocker
1.1 QMutex
QMutex类是互斥量,使得线程之间的访问可序列化。
QMutex的设计初衷是用来保护一个对象、数据结构、代码段,使得它们在同一时刻,只有一个线程有访问权限(这类似于Java synchronized 关键字)。通常最好将互斥锁与QMutexLocker一起使用,因为这样可以很容易地确保一致地执行锁定和解锁。
enum QMutex::RecursionMode (默认值为0)
枚举变量名 枚举值 描述 QMutex::Recursive 1 递归模式。在这种模式下,一个线程可以lock多次相同的互斥量,直到相应数量的unlock被调用才能被解锁。 QMutex::NonRecursive 0 非递归模式。在这种模式下,一个线程仅可以锁互斥量一次,不可递归。
基本操作函数:
void QMutex::lock(): 给互斥量上锁。如果互斥量被另一个线程加了锁,则阻塞知道另一个线程释放锁。如果是非递归模式的锁,重复上锁会造成死锁。
bool QMutex::tryLock(int timeout = 0): 试着去给一个互斥量加锁,如果这个互斥量没被锁,则返回true,如果被锁,则等待timeout时间,等待其他线程释放锁,当timeout为负数时,一直等待。
void QMutex::unlock(): 给mutex解锁,给一个未lock的mutex解锁,将有不可预知的结果,尝试去给不同的线程中的mutex解锁,会导致错误。
bool QMutex::isRecursive() const : 如果mutex为递归,则返回true。
代码实例:
QMutex mutex;
int number = 6;
// A线程中的方法
void method1()
{
mutex.lock();
number *= 5;
number /= 4;
mutex.unlock();
}
// B线程中的方法
void method2()
{
mutex.lock();
number *= 3;
number /= 2;
mutex.unlock();
}
1.2 QMutexLocker
QMutexLocker是一个用来简化互斥量锁定和解锁操作的类。
在复杂函数或者异常处理代码中互斥量的锁定和解锁容易出错和难以调试。QMutexLocker就可以应用于这些情况,确保互斥量状态总是定义明确。
应该在程序中QMutex需要被锁定处创建QMutexLocker。当QMutexLocker被创建后,互斥量就锁定了。你可以使用unlock()和relock()来解锁和重锁互斥量。如果互斥量被锁定,当QMutexLocker销毁的时候,会自动解锁互斥量。
例如,在以下这个复杂函数中,进入函数开始处锁定互斥量,在各个退出点解锁互斥量:
// 复杂函数中用QMutex的例子
int complexFunction(int flag)
{
mutex.lock();
int retVal = 0;
switch (flag) {
case 0:
case 1:
retVal = moreComplexFunction(flag);
break;
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;
}
当这个示例函数变得更复杂的时候,将增加出错的可能性。
使用QMutexLocker大大地简化代码,增加代码可读性:
// 复杂函数中用QMutexLocker的例子
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;
}
现在,当QMutexLocker对象被销毁的时候,就自动实现互斥量的解锁(因为QMutexLocker是个局部变量,当函数返回时它就被销毁)。
这原则同样适用于抛出和捕获异常的代码。在异常处理函数中,如果互斥量已经被锁定,但是在异常被抛出之前还没有其他的地方解锁互斥量,那么异常将不被捕获。
QMutexLocker也提供了一个mutex()成员函数返回QMutexLocker操作的互斥量。对于需要访问互斥量是十分有用的,比如QWaitCondition::wait()。
例如:
// QMutexLocker::mutex()用法示例
class SignalWaiter
{
private:
QMutexLocker locker;
public:
SignalWaiter(QMutex *mutex)
: locker(mutex)
{
}
void waitForSignal()
{
...
while (!signalled)
waitCondition.wait(locker.mutex());
...
}
};
基本操作函数:
QMutexLocker(QMutex *mutex): 构造一个QMutexLocker对象并锁住互斥量。当QMutexLocker被销毁的时候,互斥量将被自动解锁。如果互斥量为0,QMutexLocker不起作用。
~QMutexLocker (): 销毁QMutexLocker并且解锁在它构造函数中被锁定互斥量
QMutexLocker::mutex (): 返回在QMutexLocker构造函数中被锁定的互斥量
reloack(): 锁定一个解锁状态的互斥量
unlock(): 解锁互斥量。
2. QSemaphore
QSemaphore类是计数信号量,它是对互斥量(QMutex)功能的一种扩展。互斥量只能锁定一次,而信号量可以获取多次,信号量通常用于保护一定数量的相同资源。
信号量支持两个基本操作:acquire()和release():
acquire(n) 函数用于获取n个资源。如果没有足够多可用资源时,调用者将被阻塞直到有足够的可用资源。
release(n) 函数用于释放n个资源
其它函数:
tryAcquire() 函数用于获取资源,如果它不能获取资源,会立即返回false。
available() 函数可在任何时候返回可用资源的数量。
信号量的典型应用就是 生产者和消费者问题。
比如在餐馆里用餐,一个信号量由餐厅里的椅子数量初始化。到达餐馆就餐的人都想要一个座位。当座位越来越满,available()在减少;当人们离开,available()增加,允许更多的人坐下来就餐。
代码实例:
// 生产者与消费者问题
#include <QtCore>
#include <stdio.h>
#include <stdlib.h>
const int DataSize = 100000;
const int BufferSize = 8192;
char buffer[BufferSize];
QSemaphore freeBytes(BufferSize);
QSemaphore usedBytes;
// 生产者线程
class Producer : public QThread
{
public:
void run() override
{
qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
for (int i = 0; i < DataSize; ++i) {
freeBytes.acquire();
buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
usedBytes.release();
}
}
};
// 消费者线程
class Consumer : public QThread
{
Q_OBJECT
public:
void run() override
{
for (int i = 0; i < DataSize; ++i) {
usedBytes.acquire();
fprintf(stderr, "%c", buffer[i % BufferSize]);
freeBytes.release();
}
fprintf(stderr, "\n");
}
}
3. QReadLocker、QWriteLocker和QReadWriteLock
4.1 QReadWriteLock
Qt中的QReadWriteLock类为我们提供了读写锁的功能。
读写锁是用来保护可以被读访问和写访问的资源的一种同步工具。如果你想让多个线程同时的对资源进行读访问,但只要有一个线程要对资源进行写访问时,所有其他的线程必须等待,直到写访问完成。对于这种情况,读写锁是非常有用的。
在很多情况下,QReadWriteLock是QMutex的直接竞争对手。如果有很多并发的读操作,很少的写操作时,QReadWriteLock是一个很好的选择。
代码实例:
// QReadWriteLock(读写锁)的使用示例
QReadWriteLock lock;
void ReaderThread::run()
{
...
lock.lockForRead();
read_file();
lock.unlock();
...
}
void WriterThread::run()
{
...
lock.lockForWrite();
write_file();
lock.unlock();
...
}
为了确保写操作不会被读操作永远阻塞,当有阻塞的写操作在等待时,请求读操作会被阻塞,即使当前的锁由另一个读操作持有。同样,当锁被一个写操作持有时,另一个写操作又进来了,那么后来的写操作将会优先于所有的读操作而先获得锁。
和QMutex一样,QReadWriteLock也可以被同一个线程递归的锁定,只要在构造函数中传入一个QReadWriteLock::Recursive标志。在这种情况下,unlock()被调用的次数必须和lockForWrite()或lockForRead()被调用的次数一样多。但要注意,当递归锁定时,锁类型不能改变,也就是说,在已经锁定为写的线程中无法锁定为读(反之亦然)。(即一个线程中只能有读写中的一种操作,只能加一种锁,不允许一个线程对一个文件同时支持读写操作)。
同样,为了方便使用QReadWriteLock,Qt还提供了QReadLocker和QWriteLocker两个方便类。能使我们更方便的使用读写锁的功能。
enum QReadWriteLock::RecursionMode (默认值为0)
枚举变量名 枚举值 描述 QReadWriteLock::Recursive 1 递归模式。在这种模式下,一个线程可以lock多次相同的读写锁,直到相应数量的unlock()被调用才能被解锁。 QReadWriteLock::NonRecursive 0 非递归模式。在这种模式下,一个线程仅可以锁读写锁一次,不可递归。
基本操作函数:
QReadWriteLock::QReadWriteLock(RecursionMode recursionMode = NonRecursive): 用给定的recursionMode参数构造一个读写锁。
QReadWriteLock::~QReadWriteLock(): 销毁一个读写锁。注意,当销毁一个正在使用的读写锁时可能引起未知错误。
lockForRead():读锁定。如果其他的线程已经写锁定了,则该函数的读操作会阻塞。
lockForWrite(): 写锁定。如果其他的线程已经读锁定或者写锁定了,则该函数的写操作会阻塞。(除非创建的是递归锁类型)
bool QReadWriteLock::tryLockForRead(): 尝试读锁定。如果读锁定获得成功,返回true,否则返回false,它不会阻塞。
bool QReadWriteLock::tryLockForRead(int timeout): 尝试读锁定。如果读锁定成功,则返回true,如果获得不成功,则等待timeout时间,等待其他线程释放锁,当timeout为负数时,一直等待。
bool QReadWriteLock::tryLockForWrite(): 尝试写锁定。如果写锁定成功,则返回true,否则它立即返回false。
bool QReadWriteLock::tryLockForWrite(int timeout): 尝试写锁定。如果写锁定成功,则返回true,如果获得不成功,则等待timeout时间,等待其他线程释放锁,当timeout为负数时,一直等待。
void QReadWriteLock::unlock(): 释放锁。释放一个未锁定的锁是错误的,会引起程序中断。
4.2 QReadLocker
QReadLocker是一个用来简化读写锁中读操作的上锁和解锁操作的类。
QReadLocker(和QWriteLocker)的目的是简化QReadWriteLock的上锁和解锁。异常处理代码中读写锁的锁定和解锁容易出错和难以调试。QReadLocker就可以应用于这些情况,确保读写锁状态总是定义明确。
代码实例:
// 1.QReadLocker的使用示例
QReadWriteLock lock;
QByteArray readData()
{
QReadLocker locker(&lock);
...
return data;
}
// 2.QReadWriteLock实现代码(与上等价)
QReadWriteLock lock;
QByteArray readData()
{
lock.lockForRead();
...
lock.unlock();
return data;
}
基本操作函数:
QReadLocker(QReadWriteLock *): 创建一个QReadLocker,并且读锁定。
~QReadLocker(): 销毁QReadLocker,并且释放读锁定。
readWriteLock() const : QReadWriteLock *: 获得读写锁的指针。
relock():对未锁定的读写锁重新上读锁。
unlock(): 释放锁
4.3 QWriteLocker
QWriteLocker是一个用来简化读写锁中写操作的上锁和解锁操作的类。
QWriteLocker(和QReadLocker)的目的是简化QReadWriteLock的上锁和解锁。异常处理代码中读写锁的锁定和解锁容易出错和难以调试。QWriteLocker就可以应用于这些情况,确保读写锁状态总是定义明确。
代码实例:
// 1.QWriteLocker的使用示例
QReadWriteLock lock;
void writeData(const QByteArray &data)
{
QWriteLocker locker(&lock);
...
}
// 2.QReadWriteLock实现代码(与上等价)
QReadWriteLock lock;
void writeData(const QByteArray &data)
{
lock.lockForWrite();
...
lock.unlock();
}
基本操作函数:
QWriteLocker(QReadWriteLock *): 创建一个QWriteLocker,并且写锁定。
~QWriteLocker(): 销毁QWriteLocker,并且释放写锁定。
readWriteLock() const : QReadWriteLock *: 获得读写锁的指针。
relock():对未锁定的读写锁重新上写锁。
unlock(): 释放锁。