Qt 线程同步机制:互斥锁、信号量等
在 Qt 多线程编程中,线程同步是确保多个线程安全访问共享资源的关键技术。Qt 提供了多种线程同步机制,包括互斥锁、读写锁、信号量、条件变量等。本文将深入探讨这些同步机制的使用场景、实现原理及最佳实践。
一、互斥锁(QMutex)
1. 基本互斥锁
class Counter {
public:void increment() {QMutexLocker locker(&m_mutex);m_count++;}void decrement() {QMutexLocker locker(&m_mutex);m_count--;}int value() const {QMutexLocker locker(&m_mutex);return m_count;}private:mutable QMutex m_mutex;int m_count = 0;
};
2. 递归互斥锁
class RecursiveExample {
public:RecursiveExample() : m_mutex(QMutex::Recursive) {}void outerFunction() {QMutexLocker locker(&m_mutex);// 执行一些操作// 可以在持有锁的情况下调用 innerFunctioninnerFunction();}void innerFunction() {QMutexLocker locker(&m_mutex);// 执行一些操作}private:QMutex m_mutex;
};
3. 尝试锁定
void tryLockExample() {QMutex mutex;// 尝试锁定,立即返回if (mutex.tryLock()) {try {// 访问共享资源} finally {mutex.unlock();}} else {// 无法获取锁,执行其他操作}// 尝试锁定,超时返回if (mutex.tryLock(100)) { // 等待 100 毫秒try {// 访问共享资源} finally {mutex.unlock();}} else {// 超时,执行其他操作}
}
二、读写锁(QReadWriteLock)
1. 读写锁的基本使用
class DataCache {
public:QByteArray data() const {QReadLocker locker(&m_lock);return m_data;}void setData(const QByteArray &data) {QWriteLocker locker(&m_lock);m_data = data;}private:mutable QReadWriteLock m_lock;QByteArray m_data;
};
2. 高级读写锁应用
class ConcurrentMap {
public:QString value(const QString &key) const {QReadLocker locker(&m_lock);return m_map.value(key);}void insert(const QString &key, const QString &value) {QWriteLocker locker(&m_lock);m_map.insert(key, value);}void remove(const QString &key) {QWriteLocker locker(&m_lock);m_map.remove(key);}private:mutable QReadWriteLock m_lock;QMap<QString, QString> m_map;
};
三、信号量(QSemaphore)
1. 信号量基本原理
// 生产者-消费者示例
class ProducerConsumer {
public:ProducerConsumer(int bufferSize) : m_freeSlots(bufferSize), m_usedSlots(0) {}void produce(const QString &item) {// 等待空闲槽位m_freeSlots.acquire();// 临界区{QMutexLocker locker(&m_mutex);m_buffer.enqueue(item);}// 通知有可用项m_usedSlots.release();}QString consume() {// 等待可用项m_usedSlots.acquire();QString item;// 临界区{QMutexLocker locker(&m_mutex);item = m_buffer.dequeue();}// 通知有空槽位m_freeSlots.release();return item;}private:QQueue<QString> m_buffer;QMutex m_mutex;QSemaphore m_freeSlots;QSemaphore m_usedSlots;
};
2. 资源管理示例
class ResourcePool {
public:explicit ResourcePool(int maxResources) : m_semaphore(maxResources) {}QSharedPointer<Resource> acquireResource() {// 等待资源可用m_semaphore.acquire();// 从池中获取资源或创建新资源QMutexLocker locker(&m_mutex);if (!m_resources.isEmpty()) {QSharedPointer<Resource> resource = m_resources.takeFirst();return resource;} else {return QSharedPointer<Resource>(new Resource());}}void releaseResource(const QSharedPointer<Resource> &resource) {// 将资源放回池中QMutexLocker locker(&m_mutex);m_resources.append(resource);// 释放信号量m_semaphore.release();}private:QList<QSharedPointer<Resource>> m_resources;QMutex m_mutex;QSemaphore m_semaphore;
};
四、条件变量(QWaitCondition)
1. 条件变量基本用法
class Queue {
public:void enqueue(const QString &item) {QMutexLocker locker(&m_mutex);// 等待队列不满while (m_queue.size() >= m_maxSize) {m_notFull.wait(&m_mutex);}m_queue.enqueue(item);// 通知队列非空m_notEmpty.wakeOne();}QString dequeue() {QMutexLocker locker(&m_mutex);// 等待队列非空while (m_queue.isEmpty()) {m_notEmpty.wait(&m_mutex);}QString item = m_queue.dequeue();// 通知队列不满m_notFull.wakeOne();return item;}private:QQueue<QString> m_queue;QMutex m_mutex;QWaitCondition m_notEmpty;QWaitCondition m_notFull;int m_maxSize = 10;
};
2. 多线程任务协调
class TaskCoordinator {
public:void workerReady() {QMutexLocker locker(&m_mutex);m_readyWorkers++;// 所有工作线程都准备好了if (m_readyWorkers == m_totalWorkers) {m_allReady.wakeAll();} else {// 等待其他工作线程m_notAllReady.wait(&m_mutex);}}void startWorkers() {QMutexLocker locker(&m_mutex);// 等待所有工作线程准备好while (m_readyWorkers < m_totalWorkers) {m_allReady.wait(&m_mutex);}// 通知所有工作线程开始工作m_notAllReady.wakeAll();}private:QMutex m_mutex;QWaitCondition m_allReady;QWaitCondition m_notAllReady;int m_readyWorkers = 0;int m_totalWorkers = 0;
};
五、自旋锁(QSpinLock)
1. 自旋锁基本用法
class AtomicCounter {
public:void increment() {QSpinLocker locker(&m_spinLock);m_value++;}void decrement() {QSpinLocker locker(&m_spinLock);m_value--;}int value() const {QSpinLocker locker(&m_spinLock);return m_value;}private:mutable QSpinLock m_spinLock;int m_value = 0;
};
2. 自旋锁与互斥锁性能对比
// 测试函数
void testSpinLockVsMutex() {const int iterations = 1000000;// 测试 QMutex{QMutex mutex;int value = 0;QTime timer;timer.start();for (int i = 0; i < iterations; ++i) {QMutexLocker locker(&mutex);value++;}qDebug() << "QMutex time:" << timer.elapsed() << "ms";}// 测试 QSpinLock{QSpinLock spinLock;int value = 0;QTime timer;timer.start();for (int i = 0; i < iterations; ++i) {QSpinLocker locker(&spinLock);value++;}qDebug() << "QSpinLock time:" << timer.elapsed() << "ms";}
}
六、线程安全队列实现
1. 基于互斥锁的线程安全队列
template <typename T>
class ThreadSafeQueue {
public:void enqueue(const T &value) {QMutexLocker locker(&m_mutex);m_queue.enqueue(value);m_condition.wakeOne();}T dequeue() {QMutexLocker locker(&m_mutex);// 等待队列中有元素while (m_queue.isEmpty()) {m_condition.wait(&m_mutex);}return m_queue.dequeue();}bool tryDequeue(T &value, int timeout = 0) {QMutexLocker locker(&m_mutex);// 等待队列中有元素,直到超时if (timeout > 0 && m_queue.isEmpty()) {if (!m_condition.wait(&m_mutex, timeout)) {return false;}}if (m_queue.isEmpty()) {return false;}value = m_queue.dequeue();return true;}bool isEmpty() const {QMutexLocker locker(&m_mutex);return m_queue.isEmpty();}int size() const {QMutexLocker locker(&m_mutex);return m_queue.size();}private:QQueue<T> m_queue;mutable QMutex m_mutex;QWaitCondition m_condition;
};
2. 无锁队列实现(原子操作)
template <typename T>
class LockFreeQueue {
public:void enqueue(const T &value) {Node *newNode = new Node(value);Node *oldTail = m_tail.load();while (!m_tail.compare_exchange_weak(oldTail, newNode)) {// CAS 失败,重试}oldTail->next = newNode;}bool dequeue(T &value) {Node *oldHead = m_head.load();if (oldHead == m_tail.load()) {return false; // 队列为空}Node *newHead = oldHead->next;if (m_head.compare_exchange_weak(oldHead, newHead)) {value = oldHead->data;delete oldHead;return true;}return false; // CAS 失败,重试}private:struct Node {T data;Node *next;explicit Node(const T &value) : data(value), next(nullptr) {}};std::atomic<Node*> m_head;std::atomic<Node*> m_tail;
};
七、同步机制选择指南
同步机制 | 适用场景 |
---|---|
QMutex | 保护短时间内的关键代码段,防止多个线程同时访问共享资源 |
QReadWriteLock | 读多写少的场景,允许多个线程同时读取共享资源,但写操作需要独占访问 |
QSemaphore | 控制对有限资源的访问,如线程池、连接池等 |
QWaitCondition | 需要线程间复杂协调的场景,如生产者-消费者模型、任务同步等 |
QSpinLock | 锁持有时间极短,且线程不希望在等待锁时被挂起的场景 |
QRecursiveMutex | 同一线程需要多次获取锁的场景,如递归函数中使用锁 |
八、最佳实践
1. 避免死锁
// 错误示例:可能导致死锁
void deadlockExample() {static QMutex mutex1, mutex2;// 线程 1[&]() {QMutexLocker locker1(&mutex1);QThread::msleep(100); // 增加死锁概率QMutexLocker locker2(&mutex2);// 访问共享资源}();// 线程 2[&]() {QMutexLocker locker2(&mutex2);QThread::msleep(100); // 增加死锁概率QMutexLocker locker1(&mutex1);// 访问共享资源}();
}// 正确示例:按固定顺序获取锁
void deadlockFreeExample() {static QMutex mutex1, mutex2;// 线程 1[&]() {QMutexLocker locker1(&mutex1);QMutexLocker locker2(&mutex2);// 访问共享资源}();// 线程 2[&]() {// 按相同顺序获取锁QMutexLocker locker1(&mutex1);QMutexLocker locker2(&mutex2);// 访问共享资源}();
}
2. 最小化锁的粒度
// 不良实践:锁粒度太大
void badPractice() {QMutexLocker locker(&m_mutex);// 执行不需要锁保护的操作doSomeWork();// 只需要保护关键部分{// 关键代码段sharedResource.access();}// 执行不需要锁保护的操作doMoreWork();
}// 良好实践:锁粒度最小化
void goodPractice() {// 执行不需要锁保护的操作doSomeWork();// 只锁定关键部分{QMutexLocker locker(&m_mutex);sharedResource.access();}// 执行不需要锁保护的操作doMoreWork();
}
九、总结
Qt 提供了多种线程同步机制,每种机制都有其特定的适用场景。在实际开发中,应根据具体需求选择合适的同步机制,并遵循以下原则:
- 最小化共享资源:尽量减少线程间共享的数据,降低同步需求
- 选择合适的同步原语:根据访问模式选择互斥锁、读写锁或信号量等
- 保持锁的粒度最小:只锁定真正需要保护的代码段
- 避免死锁:按固定顺序获取锁,避免嵌套锁
- 优先使用 RAII 风格的锁管理:如 QMutexLocker、QReadLocker 等
- 考虑无锁数据结构:对于高性能要求的场景,考虑使用原子操作实现无锁数据结构
通过合理使用这些同步机制和最佳实践,可以构建高效、稳定的多线程应用,避免常见的线程安全问题。