一、线程同步概述
在多线程编程中,当多个线程需要访问共享资源或协调执行顺序时,必须使用同步机制来保证线程安全。Qt提供了多种线程同步类,主要包括:
-
QMutex - 互斥锁,提供基本的线程互斥
-
QReadWriteLock - 读写锁,优化读多写少场景
-
QWaitCondition - 条件变量,实现线程间条件等待
-
QSemaphore - 信号量,控制对多个资源的访问
-
QAtomicInteger - 原子操作,无锁编程基础
基本同步原语
1. QMutex (互斥锁)
QMutex是最基本的同步原语,用于保护共享资源,防止多个线程同时访问。
基本用法
QSharedData data;
QMutex mutex;
void ThreadA::run()
{
mutex.lock();
// 临界区 - 访问共享数据
data.modify();
mutex.unlock();
}
void ThreadB::run()
{
QMutexLocker locker(&mutex); // 自动加锁,退出作用域自动解锁
// 临界区 - 访问共享数据
data.access();
}
重要方法
| 方法 |
描述 |
|---|---|
lock() |
锁定互斥锁,如果已被锁定则阻塞 |
tryLock() |
尝试锁定互斥锁,立即返回是否成功 |
tryLock(timeout) |
尝试在指定时间内锁定互斥锁 |
unlock() |
解锁互斥锁 |
QMutexLocker |
RAII风格的锁管理类,构造时加锁,析构时自动解锁 |
2. QReadWriteLock (读写锁)
当多个线程可以同时读取但只能单个线程写入时,QReadWriteLock比QMutex更高效。
QReadWriteLock lock;
QString sharedData;
void ReaderThread::run()
{
lock.lockForRead();
// 多个读取线程可以同时进入
qDebug() << sharedData;
lock.unlock();
}
void WriterThread::run()
{
lock.lockForWrite();
// 只有一个写入线程可以进入
sharedData = "New Value";
lock.unlock();
}
高级同步机制
1. QWaitCondition (条件变量)
允许线程在某些条件满足时被唤醒,常用于生产者-消费者模式。
生产者-消费者示例
QWaitCondition bufferNotEmpty;
QWaitCondition bufferNotFull;
QMutex mutex;
QQueue<int> buffer;
const int BufferSize = 10;
void ProducerThread::run()
{
for (int i = 0; i < 100; ++i) {
mutex.lock();
while (buffer.size() == BufferSize)
bufferNotFull.wait(&mutex);
buffer.enqueue(i);
bufferNotEmpty.wakeAll();
mutex.unlock();
}
}
void ConsumerThread::run()
{
forever {
mutex.lock();
while (buffer.isEmpty())
bufferNotEmpty.wait(&mutex);
int value = buffer.dequeue();
bufferNotFull.wakeOne();
mutex.unlock();
process(value);
}
}
2. QSemaphore (信号量)
信号量是互斥锁的泛化,允许多个线程同时访问有限数量的资源。
const int DataSize = 100;
const int BufferSize = 10;
QSemaphore freeSpace(BufferSize);
QSemaphore usedSpace(0);
void ProducerThread::run()
{
for (int i = 0; i < DataSize; ++i) {
freeSpace.acquire(); // 等待有可用空间
buffer[i % BufferSize] = i;
usedSpace.release(); // 通知有新数据可用
}
}
void ConsumerThread::run()
{
for (int i = 0; i < DataSize; ++i) {
usedSpace.acquire(); // 等待有可用数据
process(buffer[i % BufferSize]);
freeSpace.release(); // 通知有空间释放
}
}
原子操作
对于简单的计数器或标志位,使用原子操作可以避免锁的开销。
QAtomicInt counter;
void WorkerThread::run()
{
for (int i = 0; i < 1000; ++i) {
counter.fetchAndAddRelaxed(1); // 原子递增
}
}
线程同步最佳实践
-
减少锁的粒度:尽量缩小临界区范围
-
避免嵌套锁:容易导致死锁
-
使用RAII管理锁:QMutexLocker等自动管理类
-
优先使用高级同步机制:如QWaitCondition、QSemaphore
-
考虑无锁设计:对性能敏感场景使用原子操作
-
避免长时间持有锁:特别是在可能阻塞的操作中
常见问题与解决方案
死锁预防
// 错误的嵌套锁
mutexA.lock();
mutexB.lock();
// ...
mutexB.unlock();
mutexA.unlock();
// 正确的顺序锁定
QMutexLocker locker1(&mutexA);
QMutexLocker locker2(&mutexB);
性能优化
// 读多写少场景使用读写锁
QReadWriteLock lock;
void readData()
{
QReadLocker locker(&lock);
// 多个线程可以同时读取
}
void writeData()
{
QWriteLocker locker(&lock);
// 只有一个线程可以写入
}
实际应用案例
线程安全的日志系统
class ThreadSafeLogger
{
public:
static ThreadSafeLogger& instance()
{
static ThreadSafeLogger logger;
return logger;
}
void log(const QString &message)
{
QMutexLocker locker(&m_mutex);
QFile file("app.log");
if (file.open(QIODevice::Append)) {
QTextStream stream(&file);
stream << QDateTime::currentDateTime().toString()
<< ": " << message << "\n";
}
}
private:
QMutex m_mutex;
ThreadSafeLogger() {} // 单例
};
总结
Qt提供了丰富的线程同步机制,开发者应根据具体场景选择合适的同步方式:
-
基本互斥:QMutex/QMutexLocker
-
读写分离:QReadWriteLock/QReadLocker/QWriteLocker
-
线程协调:QWaitCondition
-
资源池管理:QSemaphore
-
高性能原子操作:QAtomicInteger等
正确使用这些同步机制可以构建出既安全又高效的多线程Qt应用程序。记住,同步代码应该保持简单明了,复杂的同步逻辑往往是bug的温床。
二、互斥量
在多线程编程中,当多个线程需要访问共享数据或共享资源时,如果不加以保护,就会导致数据竞争,从而引发不可预知的错误和崩溃。互斥量是解决这一问题最常用的同步原语之一。
Qt 提供了两种主要方式来使用互斥量:
-
QMutex类:提供基本的加锁、解锁操作。 -
QMutexLocker类:一个辅助类,基于 RAII 思想,能自动管理QMutex的锁定和解锁,是更推荐、更安全的方式。
1. QMutex 类
QMutex 的核心思想是锁。一个线程在访问共享资源前先“锁住”互斥量,如果锁成功,它就进入临界区执行代码。如果另一个线程试图锁一个已经被锁住的互斥量,它将被阻塞,直到第一个线程解锁该互斥量。
主要成员函数:
-
void lock(): 锁定互斥量。如果互斥量已被其他线程锁定,则调用线程将被阻塞,直到互斥量可用。 -
void unlock(): 解锁互斥量。 -
bool tryLock(int timeout = 0): 尝试锁定互斥量。-
成功返回
true。 -
如果互斥量已被锁定,它不会阻塞线程,而是立即返回&n
-

最低0.47元/天 解锁文章
1220

被折叠的 条评论
为什么被折叠?



