一、线程为什么要同步
使用两个线程对一个全局变量做累加,从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; // 创建线程对象1MyThread thread2; // 创建线程对象2MyThread thread3; // 创建线程对象3thread1.start(); // 启动线程1thread2.start(); // 启动线程2thread3.start(); // 启动线程3thread1.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
================================================