Qt 线程同步:互斥量和信号量、生产者消费者问题QWaitCondition

一、线程同步概述

在多线程编程中,当多个线程需要访问共享资源或协调执行顺序时,必须使用同步机制来保证线程安全。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); // 原子递增
    }
}

线程同步最佳实践

  1. 减少锁的粒度:尽量缩小临界区范围

  2. 避免嵌套锁:容易导致死锁

  3. 使用RAII管理锁:QMutexLocker等自动管理类

  4. 优先使用高级同步机制:如QWaitCondition、QSemaphore

  5. 考虑无锁设计:对性能敏感场景使用原子操作

  6. 避免长时间持有锁:特别是在可能阻塞的操作中

常见问题与解决方案

死锁预防

// 错误的嵌套锁
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提供了丰富的线程同步机制,开发者应根据具体场景选择合适的同步方式:

  1. 基本互斥:QMutex/QMutexLocker

  2. 读写分离:QReadWriteLock/QReadLocker/QWriteLocker

  3. 线程协调:QWaitCondition

  4. 资源池管理:QSemaphore

  5. 高性能原子操作:QAtomicInteger等

正确使用这些同步机制可以构建出既安全又高效的多线程Qt应用程序。记住,同步代码应该保持简单明了,复杂的同步逻辑往往是bug的温床。

二、互斥量

在多线程编程中,当多个线程需要访问共享数据或共享资源时,如果不加以保护,就会导致数据竞争,从而引发不可预知的错误和崩溃。互斥量是解决这一问题最常用的同步原语之一。

Qt 提供了两种主要方式来使用互斥量:

  1. QMutex 类:提供基本的加锁、解锁操作。

  2. QMutexLocker 类:一个辅助类,基于 RAII 思想,能自动管理 QMutex 的锁定和解锁,是更推荐、更安全的方式。

1. QMutex 类

QMutex 的核心思想是。一个线程在访问共享资源前先“锁住”互斥量,如果锁成功,它就进入临界区执行代码。如果另一个线程试图锁一个已经被锁住的互斥量,它将被阻塞,直到第一个线程解锁该互斥量。

主要成员函数:

  • void lock(): 锁定互斥量。如果互斥量已被其他线程锁定,则调用线程将被阻塞,直到互斥量可用。

  • void unlock(): 解锁互斥量。

  • bool tryLock(int timeout = 0): 尝试锁定互斥量。

    • 成功返回 true

    • 如果互斥量已被锁定,它不会阻塞线程,而是立即返回&n

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值