在多线程编程中,常常需要确保多个线程对共享资源的访问不会产生数据竞争。为此,我们使用同步机制来保证线程安全。在Qt/C++中,常见的同步机制包括
互斥锁(QMutex
、std::mutex
)用于保护临界区,保证同一时刻只有一个线程访问共享资源。
信号量(QSemaphore
)适合控制对有限资源的并发访问。
读写锁(QReadWriteLock
)在多读少写的情况下能提供更好的性能。
原子操作(QAtomicInt
等)是轻量级的同步方式,适合简单的计数操作。
条件变量(QWaitCondition
、std::condition_variable
)则用于等待某个条件满足的线程间同步。
1. 互斥锁(QMutex / std::mutex)
互斥锁是一种常见的同步工具,用于防止多个线程同时进入临界区(共享资源的代码段)。在任何时刻,只有一个线程可以持有互斥锁并进入临界区,其他线程必须等待锁被释放后才能继续执行。
#include <QMutex>
#include <QThread>
#include <iostream>
QMutex mutex;
int sharedResource = 0;
class Worker : public QThread {
public:
void run() override {
mutex.lock();
std::cout << "Thread " << QThread::currentThreadId() << " is entering the critical section." << std::endl;
sharedResource++;
std::cout << "Shared Resource: " << sharedResource << std::endl;
mutex.unlock();
}
};
int main() {
Worker worker1, worker2;
worker1.start();
worker2.start();
worker1.wait();
worker2.wait();
return 0;
}
-
互斥锁的锁定与解锁:使用
mutex.lock()
锁定互斥锁,保证同一时刻只有一个线程能够进入修改sharedResource
的临界区。执行完临界区的代码后,必须调用mutex.unlock()
解锁。 -
线程竞争:两个线程
worker1
和worker2
竞争访问共享资源sharedResource
,通过互斥锁保证安全。
2. 信号量(QSemaphore
)
信号量是一种用于控制多个线程访问有限资源的同步机制。它允许多个线程进入临界区,但总数受到信号量的限制。可以看作是一个资源计数器,线程需要“获取”信号量才能继续执行,并在完成后“释放”信号量。
#include <QSemaphore>
#include <QThread>
#include <iostream>
QSemaphore semaphore(3); // 允许同时有3个线程进入
class Worker : public QThread {
public:
void run() override {
semaphore.acquire(); // 获取信号量
std::cout << "Thread " << QThread::currentThreadId() << " is entering." << std::endl;
QThread::sleep(1); // 模拟工作
std::cout << "Thread " << QThread::currentThreadId() << " is leaving." << std::endl;
semaphore.release(); // 释放信号量
}
};
int main() {
Worker worker1, worker2, worker3, worker4;
worker1.start();
worker2.start();
worker3.start();
worker4.start();
worker1.wait();
worker2.wait();
worker3.wait();
worker4.wait();
return 0;
}
-
信号量的获取与释放:
semaphore.acquire()
减少可用资源计数器,semaphore.release()
增加资源计数器。只有计数器大于零时,线程才能继续执行。 -
并发限制:最多有3个线程可以同时进入临界区,超过的线程必须等待其他线程释放资源。
3. 读写锁(QReadWriteLock)
读写锁允许多个线程同时读取共享资源,但写操作是互斥的,即在写入时其他线程不能进行读或写操作。这适合多读少写的场景,提升了性能。
#include <QReadWriteLock>
#include <QThread>
#include <iostream>
QReadWriteLock lock;
int sharedResource = 0;
class Reader : public QThread {
public:
void run() override {
lock.lockForRead(); // 获取读锁
std::cout << "Reader thread " << QThread::currentThreadId() << " reading: " << sharedResource << std::endl;
lock.unlock(); // 释放读锁
}
};
class Writer : public QThread {
public:
void run() override {
lock.lockForWrite(); // 获取写锁
sharedResource++;
std::cout << "Writer thread " << QThread::currentThreadId() << " writing: " << sharedResource << std::endl;
lock.unlock(); // 释放写锁
}
};
int main() {
Reader reader1, reader2;
Writer writer1;
reader1.start();
reader2.start();
writer1.start();
reader1.wait();
reader2.wait();
writer1.wait();
return 0;
}
-
读写锁:
lock.lockForRead()
允许多个线程同时读取,lock.lockForWrite()
使得写入操作期间其他读写线程必须等待。 -
读写互斥:写线程
writer1
阻塞其他读线程,直到写操作完成后其他线程才能继续读。
4. 原子操作(QAtomicInt / std::atomic)
原子操作是最轻量的同步机制之一,它通过硬件保证增减操作的原子性,不需要加锁。适合计数器等简单的共享数据操作。
#include <QAtomicInt>
#include <QThread>
#include <iostream>
QAtomicInt atomicCounter = 0;
class Worker : public QThread {
public:
void run() override {
for (int i = 0; i < 1000; ++i) {
atomicCounter.ref(); // 原子增操作
}
}
};
int main() {
Worker worker1, worker2;
worker1.start();
worker2.start();
worker1.wait();
worker2.wait();
std::cout << "Final counter: " << atomicCounter.load() << std::endl; // 输出最终计数值
return 0;
}
-
原子性操作:
atomicCounter.ref()
是一个原子操作,无需加锁。硬件保证它在多个线程间的安全性。 -
轻量同步:适合简单的增减计数操作,避免了使用锁带来的性能开销。
5. 条件变量(QWaitCondition /std::condition_variable)
条件变量用于线程间的同步,它允许线程等待某个条件的满足。当条件满足时,其他线程会被唤醒并继续执行。
#include <QMutex>
#include <QWaitCondition>
#include <QThread>
#include <iostream>
QMutex mutex;
QWaitCondition condition;
bool ready = false;
class Worker : public QThread {
public:
void run() override {
mutex.lock();
while (!ready) {
condition.wait(&mutex); // 等待条件满足
}
std::cout << "Thread " << QThread::currentThreadId() << " is processing." << std::endl;
mutex.unlock();
}
};
int main() {
Worker worker1, worker2;
worker1.start();
worker2.start();
QThread::sleep(1); // 模拟准备时间
mutex.lock();
ready = true;
condition.wakeAll(); // 唤醒所有等待线程
mutex.unlock();
worker1.wait();
worker2.wait();
return 0;
}
-
条件变量等待:
condition.wait(&mutex)
会使线程等待,并在等待期间释放互斥锁。一旦条件满足,线程会被唤醒并重新获取锁。 -
条件变量唤醒:
condition.wakeAll()
唤醒所有等待线程,线程会检查条件是否已满足并继续执行。