在多线程编程中,多个线程同时访问共享资源时,可能会出现数据不一致或者错误的情况。这时,我们需要线程同步机制来保证程序的正确性。Qt 提供了多种线程同步方式,每种方式适用于不同的场景。
1. 互斥锁(QMutex)
QMutex
是最常用的线程同步机制之一,它用于保护共享资源,确保同一时刻只有一个线程可以访问资源。互斥锁(Mutex)也叫排他锁,是一种独占的锁。
代码示例:
#include <QMutex>
#include <QThread>
#include <QDebug>
QMutex mutex; // 声明一个互斥锁
int sharedData = 0; // 共享数据
// 安全地递增共享数据
void safeIncrement() {
mutex.lock(); // 加锁,确保只有一个线程能访问共享数据
sharedData++; // 对共享数据进行操作
mutex.unlock(); // 解锁,允许其他线程访问
}
class MyThread : public QThread {
public:
void run() override {
for (int i = 0; i < 1000; ++i) {
safeIncrement(); // 调用安全递增函数
}
}
};
int main() {
MyThread thread1, thread2; // 创建两个线程
thread1.start(); // 启动线程1
thread2.start(); // 启动线程2
thread1.wait(); // 等待线程1结束
thread2.wait(); // 等待线程2结束
qDebug() << "最终共享数据的值:" << sharedData; // 输出共享数据的最终值
}
注释:
- 在多线程环境下,
sharedData
是多个线程共享的资源。我们通过QMutex
进行加锁和解锁操作,确保同一时刻只有一个线程可以访问和修改sharedData
。
2. 读写锁(QReadWriteLock)
QReadWriteLock
是一种更细粒度的锁机制。它允许多个线程同时读取数据,但在写数据时,必须独占锁。这样,在读取时多个线程可以并发执行,但写入时会阻塞其他线程的读取和写入。
代码示例:
#include <QReadWriteLock>
#include <QThread>
#include <QDebug>
QReadWriteLock lock; // 声明读写锁
int sharedData = 0; // 共享数据
// 读取共享数据
void readData() {
lock.lockForRead(); // 加读锁,允许多个线程并发读取
qDebug() << "读取共享数据:" << sharedData;
lock.unlock(); // 解锁
}
// 写入共享数据
void writeData(int value) {
lock.lockForWrite(); // 加写锁,独占锁
sharedData = value;
qDebug() << "写入共享数据:" << sharedData;
lock.unlock(); // 解锁
}
class MyThread : public QThread {
public:
void run() override {
for (int i = 0; i < 5; ++i) {
readData(); // 读取共享数据
writeData(i); // 写入共享数据
}
}
};
int main() {
MyThread thread1, thread2; // 创建两个线程
thread1.start(); // 启动线程1
thread2.start(); // 启动线程2
thread1.wait(); // 等待线程1结束
thread2.wait(); // 等待线程2结束
}
注释:
QReadWriteLock
的好处是允许多个线程同时读取数据,但写操作时会阻塞其他线程的读取和写入。适合数据不经常改变但需要频繁读取的场景。
3. 事件和信号槽机制(QEvent, QSignalEmitter)
Qt 的信号和槽机制是非常强大的一种线程间通信方式。它通过事件机制,让一个线程发出信号,另一个线程接收并响应这个信号,避免了显式的锁操作。信号槽机制通过 Qt 自己的事件循环来实现线程安全的通信。
代码示例:
worker.h
— Worker 类定义
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
#include <QThread>
#include <QDebug>
class Worker : public QObject {
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr) : QObject(parent) {}
void doWork() {
qDebug() << "工作线程中执行:" << QThread::currentThread();
}
};
#endif // WORKER_H
#### **`workerthread.h` — WorkerThread 类定义**
```cpp
#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H
#include <QThread>
#include <QObject>
#include "worker.h"
class WorkerThread : public QThread {
Q_OBJECT
public:
explicit WorkerThread(Worker *worker, QObject *parent = nullptr)
: QThread(parent), m_worker(worker) {}
protected:
void run() override {
emit workSignal();
}
signals:
void workSignal();
private:
Worker *m_worker;
};
#endif // WORKERTHREAD_H
main.cpp
— 主函数
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include "worker.h"
#include "workerthread.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Worker worker; // 创建工作对象
WorkerThread thread(&worker); // 创建工作线程
// 连接信号和槽
QObject::connect(&thread, &WorkerThread::workSignal, &worker, &Worker::doWork);
thread.start(); // 启动线程
thread.wait(); // 等待线程结束
return a.exec();
}
4. 条件变量(QWaitCondition)
QWaitCondition
允许线程在等待某个条件满足时释放锁,并在条件满足时唤醒其他线程。通常与 QMutex
一起使用,用于在多线程间传递信号和等待条件。
代码示例:
#include <QWaitCondition>
#include <QMutex>
#include <QThread>
#include <QDebug>
QMutex mutex; // 声明一个互斥锁
QWaitCondition condition; // 声明一个条件变量
bool ready = false; // 共享条件变量
// 等待条件
void waitForCondition() {
mutex.lock();
while (!ready) { // 如果条件不满足,线程会阻塞
condition.wait(&mutex); // 等待
}
qDebug() << "条件满足,线程继续执行!";
mutex.unlock();
}
// 通知条件满足
void notifyCondition() {
mutex.lock();
ready = true; // 改变条件状态
condition.wakeOne(); // 唤醒一个等待的线程
mutex.unlock();
}
int main() {
QThread* thread1 = new QThread();
QThread* thread2 = new QThread();
QObject::connect(thread1, &QThread::started, waitForCondition); // 连接信号和槽
QObject::connect(thread2, &QThread::started, notifyCondition); // 连接信号和槽
thread1->start(); // 启动线程1
thread2->start(); // 启动线程2
thread1->wait(); // 等待线程1结束
thread2->wait(); // 等待线程2结束
}
注释:
QWaitCondition
适用于需要线程等待某个条件满足后再继续执行的情况。thread1
会等待ready
变量为真,然后继续执行,而thread2
会改变条件并唤醒thread1
。
5. 原子操作(QAtomicInt 和 QAtomicPointer)
QAtomicInt
和 QAtomicPointer
提供了线程安全的原子操作,不需要加锁即可安全地操作共享变量。适用于计数器等简单的数值操作。
代码示例:
#include <QAtomicInt>
#include <QThread>
#include <QDebug>
QAtomicInt counter(0); // 声明原子计数器
// 原子递增计数器
void incrementCounter() {
counter.fetchAndAddOrdered(1); // 原子递增操作
}
class MyThread : public QThread {
public:
void run() override {
for (int i = 0; i < 1000; ++i) {
incrementCounter(); // 调用原子递增
}
}
};
int main() {
MyThread thread1, thread2; // 创建两个线程
thread1.start(); // 启动线程1
thread2.start(); // 启动线程2
thread1.wait(); // 等待线程1结束
thread2.wait(); // 等待线程2结束
qDebug() << "最终计数器的值:" << counter; // 输出计数器的最终值
}
注释:
QAtomicInt
允许我们进行线程安全的计数操作,无需加锁,适用于高效的原子操作。
线程同步方法对比
同步方法 | 优点 | 缺点 | 最适用场景 |
---|---|---|---|
QMutex | 简单、直观,适合保护共享资源 | 每次访问资源都需要加锁,可能会影响性能 | 需要保证共享资源被多个线程安全访问的场景 |
QReadWriteLock | 允许多个线程并发读取,适用于读多写少的情况 | 写入时需要独占锁,可能会导致读取阻塞 | 数据经常被读取,但更新不频繁的场景 |
信号与槽 | 实现跨线程通信,不需要显式的锁 | 适用于信号发出的线程和接收的线程需要有明确的关联 | 需要线程间通信,且希望简化锁操作的场景 |
QWaitCondition | 允许线程等待条件满足时阻塞和唤醒其他线程 | 比较适合于等待某个条件的场景,使用复杂 | 线程需要等待特定条件发生的场景 |
原子操作 | 高效、简单,避免了使用锁的复杂性 | 只适用于简单的数值或指针操作 | 高性能计数器、指针操作等简单数值操作的场景 |