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

多线程编程:线程间的同步与通信

多线程编程:线程间的同步与通信


一、概述

在多线程编程中,必须解决以下两个核心问题:

  1. 线程间的互斥:确保共享资源在同一时刻只被一个线程访问,防止数据竞争。
  2. 线程间的同步通信:使线程之间按照指定顺序执行,以实现任务协调和依赖关系。

二、线程间的互斥

1. 静态条件与临界区

  • 静态条件:多线程程序在不同执行顺序下可能产生不一致的结果。
  • 临界区:代码中访问共享资源的部分。
    • 解决方法:每次只能允许一个线程进入临界区。

2. 互斥锁(Mutex)

  • 作用
    • 保证临界区代码段的原子性。
    • 防止多个线程同时访问共享资源。
  • 实现方式
    • 使用 std::mutex 加锁和解锁。
    • 对于较小的临界区,可以使用轻量级的无锁机制(如 CAS)。

三、线程间的同步通信

1. 同步通信的必要性

  • 多线程程序中,线程的执行顺序由操作系统调度决定,通常是不确定的。
  • 在某些场景下,线程需要依赖其他线程的执行结果。例如:
    • 生产者-消费者问题:生产者需要将数据生产完毕后通知消费者进行消费。

2. 条件变量(Condition Variable)

  • 作用:实现线程间的同步通信。
  • 特点
    • 需要与 std::mutex 搭配使用。
    • 提供 waitnotify 方法,实现线程间的协调。

四、生产者-消费者模型

1. 问题描述

生产者线程负责生产数据,并将数据放入队列;消费者线程负责从队列中取出数据并消费。

  • 目标
    • 保证生产者和消费者交替工作。
    • 防止队列为空时消费者消费数据或队列溢出时生产者继续生产。

2. 代码实现

全局变量
std::queue<int> q;                  // 共享队列
std::mutex mtx;                     // 互斥锁
std::condition_variable cv;         // 条件变量
生产者函数
void producer() {
    for (int i = 1; i <= 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx); // 加锁
        q.push(i);                              // 生产数据
        std::cout << "Produced: " << i << std::endl;
        cv.notify_all();                        // 通知消费者
        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产时间
    }
}
消费者函数
void consumer() {
    for (int i = 1; i <= 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);               // 加锁
        cv.wait(lock, [] { return !q.empty(); });             // 等待队列非空
        int value = q.front();                                // 获取数据
        q.pop();                                              // 移除数据
        std::cout << "Consumed: " << value << std::endl;      // 打印消费日志
        lock.unlock();                                        // 解锁
        cv.notify_all();                                      // 通知生产者
        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟消费时间
    }
}
主函数
int main() {
    std::thread t1(producer); // 创建生产者线程
    std::thread t2(consumer); // 创建消费者线程
    t1.join();                // 等待生产者线程完成
    t2.join();                // 等待消费者线程完成
    return 0;
}

3. 核心逻辑

同步通信的实现
  1. 生产者和消费者的协调

    • 生产者生产数据后通过 cv.notify_all() 通知消费者。
    • 消费者消费数据后通过 cv.notify_all() 通知生产者。
  2. 防止竞争和死锁

    • 使用 std::mutex 确保队列的操作线程安全。
    • cv.wait 确保消费者只有在队列非空时才执行操作。

4. 状态变化分析

事件状态行为
消费者发现队列为空阻塞等待生产者生产数据后被通知
生产者生产数据队列非空,通知消费者消费者从等待状态转为阻塞状态,等待获取锁继续执行
消费者消费数据队列可能为空,通知生产者生产者从等待状态转为阻塞状态,等待获取锁继续执行

6. 注意事项

  1. 线程安全

    • 队列的所有操作(如 pushpop)必须在加锁状态下进行。
  2. 条件变量使用

    • wait 必须传入一个 std::unique_lock<std::mutex> 对象。
    • notify_all 应用于多个消费者时,notify_one 适用于单一消费者场景。
  3. 死锁防范

    • 确保每个线程在进入 wait 状态前释放锁。

五、总结

  1. 互斥锁的作用

    • 确保线程安全,避免共享资源的并发访问导致的数据竞争。
  2. 条件变量的作用

    • 线程间的同步通信,协调线程的执行顺序。
  3. 核心操作

    • std::mutex:加锁与解锁。
    • std::condition_variable:线程等待(wait)与通知(notify_allnotify_one)。
  4. 生产者-消费者模型的实现

    • 使用共享队列进行数据传递。
    • 条件变量协调线程之间的依赖顺序。

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

相关文章:

  • 怎样使用树莓派自己搭建一套ADS-B信号接收系统
  • 蚁群算法 (Ant Colony Optimization) 算法详解及案例分析
  • 基于JAVA的微信点餐小程序设计与实现(LW+源码+讲解)
  • 10倍数据交付提升 | 通过逻辑数据仓库和数据编织高效管理和利用大数据
  • 【面试总结】FFN(前馈神经网络)在Transformer模型中先升维再降维的原因
  • C语言二级
  • 《向量数据库指南》——Mlivus Cloud:OPPO的向量数据库选型秘籍
  • AGameModeBase和游戏模式方法
  • 03、Node.js安装及环境配置
  • 如何在自动化安全测试中,实现多工具集成与数据融合,以提高对Spring Boot应用程序安全漏洞的检测效率与准确性?
  • C++(十一)
  • Spring Security集成JWT
  • 【数学建模】论文排版教程
  • Linix学习一
  • Qt中实现可视化界面的TCP SYN扫描(改进版)
  • Lumos学习王佩丰Excel第二十讲:图表基础
  • 黑马程序员Java项目实战《苍穹外卖》Day09
  • Java集合(三)- Stack Queue
  • 如何用python获取图像
  • ADI的DSP用CCES来调试,仿真器TEST第一步“Opening Emulator Interface”报错,解决办法。
  • Chrome 中小于 12px 文字的实现方式与应用场景详解
  • 机器学习周报(12.2-12.8)
  • C# NLog 配置ElasticSearch
  • 【JAVA】Java高级:Spring框架与Java EE—Spring框架概述(控制反转、依赖注入)
  • 链表OJ题型讲解与总结
  • 【金融贷后】贷后核心风险指标有哪些?