当前位置: 首页 > article >正文

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() 唤醒所有正在等待条件的线程,使它们重新进入竞争状态。

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

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

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


http://www.kler.cn/a/318203.html

相关文章:

  • 【问卷调研】HarmonyOS SDK开发者社区用户需求有奖调研
  • Python入门(4)--流程控制(下)
  • MySQL【三】
  • 【JavaScript】为 setInterval()定义变量,存储ID
  • 【C#设计模式(4)——构建者模式(Builder Pattern)】
  • 传奇996_19——常用函数
  • Shiro-550—漏洞分析(CVE-2016-4437)
  • 详解QT插件机制
  • ARM/Linux嵌入式面经(三三):大疆
  • zabbix email 告警
  • [大语言模型-论文精读] ACL2024-长尾知识在检索增强型大型语言模型中的作用
  • Invalid Executable The executable contains bitcode
  • 报错error: RPC failed,curl 16 Error in the HTTP2 framing layer解决方法
  • 自动化学习3:日志记录及测试报告的生成--自动化框架搭建
  • 数据库课程 CMU15-445 2023 Fall Project-2 Extendible Hash Index
  • WebAssembly (Wasm) 与 JavaScript 字符串交互
  • shardingjdbc分库分表原理
  • 实战16-RVP定义完成适配
  • rocky9.2的lvs的NAT模式下的基本使用的详细示例
  • SpringBoot使用@Async注解,实现异步任务
  • 002.k8s(Kubernetes)一小时快速入门(先看docker30分钟)
  • WPF经典面试题全集
  • JavaEE: 深入探索TCP网络编程的奇妙世界(一)
  • 【MySQL】数据类型【mysql当中各自经典的数据类型的学习和使用】
  • Leetcode 136 只出现一次的数字
  • EfficientFormer实战:使用EfficientFormerV2实现图像分类任务(一)