自学内容网 自学内容网

Qt/C++ 多线程同步机制详解及应用

在多线程编程中,线程之间共享资源可能会导致数据竞争和不一致的问题。因此,采用同步机制确保线程安全至关重要。在Qt/C++中,常见的同步机制有:互斥锁(QMutexstd::mutex)、信号量(QSemaphore)、读写锁(QReadWriteLock)、原子操作(QAtomicInt 等)以及条件变量(QWaitConditionstd::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;
}
解释:
  1. 互斥锁机制:使用 mutex.lock() 锁定,mutex.unlock() 解锁,保证共享资源在某个线程执行完后才可被其他线程访问。
  2. 线程独占性:多个线程同时访问共享资源时,只有获取到锁的线程能进入临界区,避免竞争条件。

2. 信号量(QSemaphore

信号量是一种同步机制,用于控制多个线程同时访问有限数量的资源。与互斥锁不同,信号量可以允许多个线程并发访问,直到信号量资源用尽。

示例代码及注释
#include <QSemaphore>
#include <QThread>
#include <iostream>

// 初始化信号量,允许最多3个线程同时进入临界区
QSemaphore semaphore(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;
}
解释:
  1. 并发控制:信号量通过 acquire() 获取资源,release() 释放资源,控制多个线程并发执行。
  2. 资源计数:当信号量值为零时,其他线程必须等待信号量被释放才能继续执行。

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;
}
解释:
  1. 多读单写lock.lockForRead() 允许多个线程同时读取,而 lock.lockForWrite() 则保证写操作期间的独占访问。
  2. 适用场景:在多读少写的场景下,读写锁可以提高效率。

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;
}
解释:
  1. 轻量同步atomicCounter.ref() 是一个原子操作,不需要使用互斥锁,适合简单的递增或递减操作。
  2. 性能提升:相比于使用锁,原子操作的性能开销更小,非常适合对共享资源进行计数的场景。

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;
}
解释:
  1. 条件等待:线程使用 condition.wait() 进入等待状态,直到条件满足时被唤醒。
  2. 唤醒机制condition.wakeAll() 唤醒所有正在等待条件的线程,使它们重新进入竞争状态。

在多线程编程中,选择合适的同步机制是确保数据安全和程序高效运行的关键。根据不同的应用场景,互斥锁、信号量、读写锁、原子操作和条件变量各有优缺点:

  • 互斥锁 适用于保护临界区,确保共享资源的独占访问;
  • 信号量 控制并发资源的访问数量;
  • 读写锁 适合多读少写的场景,提升读取性能;
  • 原子操作 在简单的共享数据操作中高效且轻量;
  • 条件变量 允许线程等待某个条件的满足,以实现灵活的线程同步。

合理使用这些工具能够有效避免多线程编程中的竞争条件,确保程序的安全和性能。


原文地址:https://blog.csdn.net/chenai886/article/details/142393335

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!