线程同步的方式
- QMutex 互斥锁
- QReadWriteLock 读写锁
- QSemaphore 信号量
- QWaitCondition 条件等待
1.QMutex
创建一个QMutex对象:
QMutex mutex;
在需要保护共享资源的代码段之前,调用lock()方法锁定互斥体:
mutex.lock();
如果互斥体已被其他线程锁定,则当前线程将被阻塞,直到互斥体可用。
访问共享资源,进行必要的读取或写入操作。
在完成对共享资源的访问后,调用unlock()方法释放互斥体:
mutex.unlock();
这将允许其他线程获取互斥体并访问共享资源。
代码示例:
#include <QMutex>
#include <QThread>
#include <QDebug>
// 共享资源
int sharedValue = 0;
// 互斥体
QMutex mutex;
// 线程类
class MyThread : public QThread
{
public:
void run() override
{
for (int i = 0; i < 5; ++i) {
mutex.lock();
++sharedValue; // 对共享资源进行写操作
qDebug() << "Thread ID:" << currentThreadId() << "Shared Value:" << sharedValue;
mutex.unlock();
msleep(1000);
}
}
};
int main()
{
MyThread thread1;
MyThread thread2;
thread1.start();
thread2.start();
thread1.wait();
thread2.wait();
return 0;
}
2.QReadWriteLock 读写锁
QMutexLocker 构造函数接受一个互斥量作为参数并将其锁定,析构函数则将此互斥量解锁,所以在QMutexLocker实例变量的生存周期内的代码段得到保护,自动进行互斥量的锁定和解锁
// 创建一个QMutexLocker对象,并传入要锁定的互斥锁
QMutexLocker locker(&mutex);
QReadWriteLock是一种线程同步工具,用于实现读写锁定机制。它允许多个线程同时对共享资源进行读操作,但只允许一个线程进行写操作。这种机制可以提高并发性能,并且适用于读操作频繁但写操作较少的场景。
QReadWriteLock通常包含两个锁:读锁和写锁。当一个线程获取读锁时,其他线程也可以获取读锁,从而允许并发的读操作。当一个线程获取写锁时,其他线程无法获取读锁或写锁,从而保证了独占式的写操作。
#include <QCoreApplication>
#include <QReadWriteLock>
#include <QThread>
QReadWriteLock lock;
int sharedData = 0;
void readData()
{
lock.lockForRead();
// 读取共享资源
qDebug() << "Read Data:" << sharedData;
lock.unlock();
}
void writeData()
{
lock.lockForWrite();
// 修改共享资源
sharedData++;
qDebug() << "Write Data:" << sharedData;
lock.unlock();
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 创建多个读线程和写线程
QList<QThread*> threads;
for (int i = 0; i < 3; ++i) {
QThread* thread = new QThread(&a);
threads.append(thread);
QObject::connect(thread, &QThread::started, readData);
thread->start();
}
for (int i = 0; i < 2; ++i) {
QThread* thread = new QThread(&a);
threads.append(thread);
QObject::connect(thread, &QThread::started, writeData);
thread->start();
}
// 等待所有线程执行完成
for (QThread* thread : threads) {
thread->wait();
}
return a.exec();
}
3.基于条件变量的线程同步
QWaitCondition与QMutex结合,可以使一个线程在满足一定条件时通知其他多个线程,使他们及时做出响应,这样比只使用互斥量的效率高一些,QWaitCondition一般用于"生产者\消费者"模型
QWaitCondition提供的函数
void wait(QMutex *mutex, unsigned long time = ULONG_MAX)
等待条件满足,直到其他线程调用wakeOne()或wakeAll()方法唤醒当前线程。该函数会自动释放传入的mutex,并在被唤醒后重新获得mutex。
void wakeAll()
唤醒所有等待在该条件上的线程。
void wakeOne()
唤醒一个等待在该条件上的线程。
bool wait(QMutex *mutex, unsigned long time = ULONG_MAX)
等待条件满足,直到其他线程调用wakeOne()或wakeAll()方法唤醒当前线程。该函数会自动释放传入的mutex,并在被唤醒后重新获得mutex。
与第一种wait函数不同的是,该函数返回一个bool值,表示等待是否成功。
代码示例:生产者消费者模型
#define DATASIZE 100
#define BUFFERSIZE 8
int buffer[BUFFERSIZE]; //缓冲区
QWaitCondition bufferNotEmpty; //非空
QWaitCondition bufferNotFull; //非满
QMutex mutex; //互斥锁
int numUsable = 0; //可用数据量
int index = 0; //下表
//生产者线程
class ProducerThread: public QThread{
protected:
void run( ){
for(int i = 0;i < DATASIZE; i++)
{
mutex.lock();
if(numUsable == BUFFERSSIZE)//缓冲区已满
{
qDebug()<<"缓冲区已满,等待....";
bufferNotFull.wait(&mutex);//进入等待并释放mutex,等待条件成立
}
buffer[i % BUFFERSIZE] = i+1;//缓冲区未满,向缓冲区写入数字
++numUsable;
qDebug()<<"生产者产生了数据:"<<i+1;
msleep((Qrand()%5+1)*100);
mutex.unlock();
bufferNotEmpty.wakeAll();
//唤醒正在等待缓冲区有数据(bufferNotEmpty)条件成立的线程
}
}
}
//消费者线程
class ConsumerThread: Qthread()
{
protect:
void run()
{
while(true)
{
mutex.lock();
if(numUsable == 0) //缓冲区已空
qDebug<<"缓冲区已空,等待...";
bufferNotEmpty.wait(&mutex);
//等待缓冲区有数据(bufferNotEmpty)条件成立
}
qDebug<<"消费者"<<currentThreadID()<<"消费了数据:"<<buffer[index];
index = (++index)%BUFFERSIZE;
--numUserable;
msleep((Qrand()%5+1)*100);
mutex.unlock();
bufferNotFull.wakeAll();
//唤醒等待缓冲有空位(bufferNotFull)条件成立的线程
}
}
int main(int argc,char *argv[])
{
QCoreApplication a(argc,argv);
qsrand(QTime::currentTime().msec());
ProducerThread producer;
ConsumerThread consumer1;
ConsumerThread sonsumer2;
producer.start();
consumer1.start();
consumer2.start();
producer.wait();
consumer1.wait();
consumer2.wait();
//主线程中等待producer\consumer1\consumer2线程执行完毕并发送了finished()信号后才会继续执行后续代码。
}
4.基于信号量的同步
QSemaphore可以用来控制多个线程对共享资源的访问。QSemaphore维护了一个内部的计数器,用来表示可用的资源数量,线程可以通过获取和释放资源来实现同步。
QSemaphore使用情况:
- 控制并发线程数:可以限制同时执行的线程数量,比如连接池中最大连接数的控制。
- 线程间同步:可以用于线程之间的协调和同步,确保线程按照指定的顺序执行。
- 信号量机制:可以用于实现生产者-消费者问题等场景。
常用函数:
- 构造函数:QSemaphore(int n) - 创建一个信号量对象,并初始化计数器为n。
- acquire():请求一个资源,如果当前计数器大于0,则立即返回;否则进入阻塞状态,直到有资源可用。
- release():释放一个资源,增加计数器的值,并唤醒可能在等待资源的线程。
- release(int n):释放多个资源,增加计数器的值,并唤醒可能在等待资源的线程。
- available():获取当前可用的资源数量,即计数器的值。
- acquireTimeout(int timeout):请求一个资源,如果当前计数器大于0,则立即返回;否则进入阻塞状态,直到有资源可用或者超过指定的超时时间。
#define DATASIZE 100
#define BUFFERSIZE 8
int buffer[BUFFERSIZE]; //缓冲区
QSemphore freeSpaace(BUFFERSIZE);//生产者信号量
QSemphore dataNum(0);//消费者信号量
//生产者线程
class ProducerThread: public QThread
{
protected:
void run()
{
for(int i = 0;i < DATASIZE; i++)
{
freeSpace.acquire(1);//获取一个生产者信号量
buffer[i % BUFFERSIZE] = i+1;
qDebug()<<"生产者产生了数据:"<<i + 1;
//生产者产生了一个数字,对于消费者而言,就是多了一个可供消费的数字
dataNum.release(1);
msleep((Qrand()%5+1)*100);
}
}
}
//消费者线程
class ConsumerThread: public Qthread
{
protected:
void run()
{
for(int i = 0;i < DATASIZE; i++)
{
dataNum.acquire(1);//获取一个消费者信号量
qDebug()<<"消费者消费了数据:"<<buffer[i % BUFFERSIZE];
//消费者消费了一个数字,对于生产者而言多了一个可以生产的空位
freeSpace.release(1);
msleep((Qrand()%5+1)*100);
}
}
}
int main(int argc,char *argv[])
{
QCoreApplication a(argc,argv);
qsrand(QTime::currentTime().msec());
ProducerThread producer;
ConsumerThread consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();