Qt - 多线程之线程同步
一、线程为什么要同步
使用两个线程对一个全局变量做累加,从0加到10,所以只要每个线程累加到5就行。代码如下所示:
#include <QApplication>
#include <QThread>
#include <QDebug>
// 定义共享资源
int sharedValue = 0;
// 定义一个线程类
class MyThread : public QThread
{
public:
void run() override
{
for(int i = 0; i < 5; i++)
{
sharedValue++; // 访问共享资源
qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Shared Value: " << sharedValue;
msleep(1000); // 线程休眠1秒
}
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyThread thread1;
MyThread thread2;
thread1.start();
thread2.start();
thread1.wait();
thread2.wait();
qDebug() << "Final Shared Value: " << sharedValue;
return a.exec();
}
运行结果:
可以看到两个线程不是交替着执行的,会重复执行,所以并不是我们想要的结果。一般这种问题需要采用线程同步的方式。
结果分析: 明显看出在未加锁情况下对临界资源的访问出现混乱的结果。
二、线程同步方式
1、QMutex(互斥锁)
互斥锁用于保护共享资源,确保在同一时间只有一个线程能够访问该资源。线程在访问共享资源之前需要获取互斥锁,使用完后再释放互斥锁,以确保同一时间只有一个线程在执行关键代码段。
QMutex的使用步骤如下:
1.创建QMutex对象:在需要进行线程同步的地方,首先创建一个QMutex对象。
QMutex mutex;
2.获取互斥锁:在访问共享资源之前,线程需要获取互斥锁。使用lock()
方法获取互斥锁。如果互斥锁已被其他线程占用,当前线程会被阻塞,直到互斥锁被释放。
mutex.lock();
3.访问共享资源:获取到互斥锁后,线程可以安全地访问共享资源。
// 访问共享资源的代码
4.释放互斥锁:在线程完成对共享资源的访问之后,需要释放互斥锁,以便其他线程可以获取到互斥锁进行访问
mutex.unlock();
示例代码:
#include <QApplication>
#include <QThread>
#include <QDebug>
#include <QMutex>
// 定义共享资源
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: " << QThread::currentThreadId() << " - Shared Value: " << sharedValue;
msleep(1000); // 线程休眠1秒
mutex.unlock(); // 解锁
}
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyThread thread1;
MyThread thread2;
thread1.start();
thread2.start();
thread1.wait();
thread2.wait();
qDebug() << "Final Shared Value: " << sharedValue;
return a.exec();
}
运行效果:
2、QReadWriteLock(读写锁)
读写锁是Qt中用于实现读写线程同步的一种机制。它提供了一种更高效的方式来管理对共享资源的读写操作,允许多个线程同时进行读操作,但只允许一个线程进行写操作。
QReadWriteLock的使用步骤如下:
1.创建QReadWriteLock对象:在需要进行读写线程同步的地方,首先创建一个QReadWriteLock对象。
QReadWriteLock rwLock;
2.获取读锁:当线程需要进行读操作时,使用lockForRead()
方法获取读锁。多个线程可以同时获取读锁,以进行并发的读操作。
rwLock.lockForRead();
3.进行读操作:获取到读锁后,线程可以安全地进行读操作,访问共享资源
// 进行读操作的代码
4.释放读锁:在读操作完成后,使用unlock()
方法释放读锁,允许其他线程获取读锁。
rwLock.unlock();
5.获取写锁:当线程需要进行写操作时,使用lockForWrite()
方法获取写锁。写锁是独占的,只允许一个线程获取写锁进行写操作,其他线程需要等待写锁的释放。
rwLock.lockForWrite();
6.进行写操作:获取到写锁后,线程可以安全地进行写操作,修改共享资源。
// 进行写操作的代码
7.释放写锁:在写操作完成后,使用unlock()
方法释放写锁,允许其他线程获取读锁或写锁。
rwLock.unlock();
示例代码:
#include <QApplication>
#include <QThread>
#include <QDebug>
#include <QReadWriteLock>
QString sharedData; // 共享数据变量
QReadWriteLock rwLock; // 读写锁
// 读取操作线程
class ReaderThread : public QThread
{
public:
void run() override
{
rwLock.lockForRead(); // 以读取方式加锁
qDebug() << "Read Data: " << sharedData; // 输出读取的数据
rwLock.unlock(); // 释放锁
}
};
// 写入操作线程
class WriterThread : public QThread
{
public:
void run() override
{
rwLock.lockForWrite(); // 以写入方式加锁
sharedData = "Hello, world!"; // 写入数据
qDebug() << "Write Data: " << sharedData; // 输出写入的数据
rwLock.unlock(); // 释放锁
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 创建读取线程和写入线程
WriterThread writer;
ReaderThread reader;
// 启动线程
writer.start(); // 启动写入线程
reader.start(); // 启动读取线程
// 等待线程结束
writer.wait(); // 等待写入线程结束
reader.wait(); // 等待读取线程结束
return a.exec();
}
执行结果:
3、QWaitCondition(等待条件)
QWaitCondition是Qt中用于线程同步的一种机制,它允许线程等待特定条件的发生,并在条件满足时被唤醒继续执行。QWaitCondition通常与QMutex一起使用,以提供更复杂的线程同步机制。
QWaitCondition的使用步骤如下:
1.创建QWaitCondition对象:在需要进行线程同步的地方,首先创建一个QWaitCondition对象。
QWaitCondition condition;
2.创建QMutex对象:为了保护条件的读写操作,创建一个QMutex对象。
QMutex mutex;
3.在等待条件的线程中等待:在线程需要等待特定条件的发生时,使用wait()
方法使线程进入等待状态。
mutex.lock(); condition.wait(&mutex); mutex.unlock();
在调用wait()
方法之前,需要先获取到互斥锁(QMutex)。这样可以确保线程在等待之前能够安全地访问和检查条件。
4.在其他线程中满足条件并唤醒等待线程:当某个条件满足时,通过wakeOne()
或wakeAll()
方法唤醒等待的线程。
mutex.lock(); condition.wakeOne(); // 或者使用 condition.wakeAll(); mutex.unlock();
示例代码:
#include <QApplication>
#include <QThread>
#include <QDebug>
#include <QWaitCondition>
#include <QMutex>
#include <QQueue>
QMutex mutex; // 创建一个互斥锁,确保线程安全
QWaitCondition queueNotEmpty; // 创建一个条件变量,表示队列非空
QQueue<int> queue; // 创建一个队列用于存储数据
// 生产者线程
class ProducerThread : public QThread
{
public:
void run() override
{
for (int i = 0; i < 10; ++i) {
// 生产数据并加入队列
{
QMutexLocker locker(&mutex); // 加锁
queue.enqueue(i); // 生产数据并加入队列
qDebug() << "Produced: " << i;
queueNotEmpty.wakeOne(); // 通知消费者队列非空
}
msleep(100); // 休眠一段时间
}
}
};
// 消费者线程
class ConsumerThread : public QThread
{
public:
void run() override
{
for (int i = 0; i < 10; ++i) {
// 检查队列是否为空,如果为空则等待
{
QMutexLocker locker(&mutex); // 加锁
while (queue.isEmpty()) {
queueNotEmpty.wait(&mutex); // 等待条件变量,直到队列非空
}
int value = queue.dequeue(); // 从队列中取出数据
qDebug() << "Consumed: " << value;
}
msleep(200); // 休眠一段时间
}
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 创建生产者线程和消费者线程
ProducerThread producer;
ConsumerThread consumer;
// 启动线程
producer.start();
consumer.start();
// 等待线程结束
producer.wait();
consumer.wait();
return a.exec();
}
执行结果:
4、QSemaphore(信号量)
QSemaphore 是 Qt 中用于实现信号量的类,用于控制对共享资源的访问数量。它可以用来限制同时访问某一资源的线程数量,也可以用于线程之间的同步。QSemaphore 可以被获取和释放,当信号量的值为正时,线程可以获得该信号量;当信号量的值为零时,线程将被阻塞,直到有线程释放信号量。通过获取和释放信号量,可以实现线程之间的协调和资源的管理。
示例代码:
#include <QApplication>
#include <QThread>
#include <QDebug>
#include <QSemaphore>
QSemaphore semaphore(2); // 定义能够同时访问资源的线程数量为2的信号量
class MyThread : public QThread // 定义一个线程类
{
public:
void run() override
{
if(semaphore.tryAcquire())
{ // 尝试获取信号量
qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Acquired Semaphore"; // 输出线程ID和已获取信号量消息
sleep(2); // 线程休眠2秒
qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Releasing Semaphore"; // 输出线程ID和释放信号量消息
semaphore.release(); // 释放信号量
}
else
{
qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Semaphore not acquired"; // 输出线程ID和未获取信号量消息
}
}
};
int main(int argc, char *argv[]) // 主函数
{
QApplication a(argc, argv); // 创建应用程序对象
MyThread thread1; // 创建线程对象1
MyThread thread2; // 创建线程对象2
MyThread thread3; // 创建线程对象3
thread1.start(); // 启动线程1
thread2.start(); // 启动线程2
thread3.start(); // 启动线程3
thread1.wait(); // 等待线程1结束
thread2.wait(); // 等待线程2结束
thread3.wait(); // 等待线程3结束
return a.exec(); // 执行应用程序事件循环
}
执行结果:
当QSemaphore semaphore(2):
当QSemaphore semaphore(3):
================================================
参考文档:https://blog.csdn.net/qq_45790916/article/details/136390241
参考文档:https://www.cnblogs.com/TechNomad/p/17439960.html
================================================