在多線程編程中,多個線程同時訪問共享資源時,可能會出現數據不一致或者錯誤的情況。這時,我們需要線程同步機制來保證程序的正確性。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(); // 啟動線程1thread2.start(); // 啟動線程2thread1.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(); // 啟動線程1thread2.start(); // 啟動線程2thread1.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(); // 啟動線程1thread2->start(); // 啟動線程2thread1->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(); // 啟動線程1thread2.start(); // 啟動線程2thread1.wait(); // 等待線程1結束thread2.wait(); // 等待線程2結束qDebug() << "最終計數器的值:" << counter; // 輸出計數器的最終值
}
注釋:
QAtomicInt
允許我們進行線程安全的計數操作,無需加鎖,適用于高效的原子操作。
線程同步方法對比
同步方法 | 優點 | 缺點 | 最適用場景 |
---|---|---|---|
QMutex | 簡單、直觀,適合保護共享資源 | 每次訪問資源都需要加鎖,可能會影響性能 | 需要保證共享資源被多個線程安全訪問的場景 |
QReadWriteLock | 允許多個線程并發讀取,適用于讀多寫少的情況 | 寫入時需要獨占鎖,可能會導致讀取阻塞 | 數據經常被讀取,但更新不頻繁的場景 |
信號與槽 | 實現跨線程通信,不需要顯式的鎖 | 適用于信號發出的線程和接收的線程需要有明確的關聯 | 需要線程間通信,且希望簡化鎖操作的場景 |
QWaitCondition | 允許線程等待條件滿足時阻塞和喚醒其他線程 | 比較適合于等待某個條件的場景,使用復雜 | 線程需要等待特定條件發生的場景 |
原子操作 | 高效、簡單,避免了使用鎖的復雜性 | 只適用于簡單的數值或指針操作 | 高性能計數器、指針操作等簡單數值操作的場景 |