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

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


一、概述

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

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

二、线程间的互斥

1. 静态条件与临界区

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

2. 互斥锁(Mutex)

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

三、线程间的同步通信

1. 同步通信的必要性

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

2. 条件变量(Condition Variable)

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

四、生产者-消费者模型

1. 问题描述

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

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

2. 代码实现

全局变量
cpp 复制代码
std::queue<int> q;                  // 共享队列
std::mutex mtx;                     // 互斥锁
std::condition_variable cv;         // 条件变量
生产者函数
cpp 复制代码
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)); // 模拟生产时间
    }
}
消费者函数
cpp 复制代码
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)); // 模拟消费时间
    }
}
主函数
cpp 复制代码
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. 生产者-消费者模型的实现

    • 使用共享队列进行数据传递。
    • 条件变量协调线程之间的依赖顺序。
相关推荐
Biomamba生信基地几秒前
R语言基础| 回归分析
开发语言·回归·r语言
黑客-雨15 分钟前
从零开始:如何用Python训练一个AI模型(超详细教程)非常详细收藏我这一篇就够了!
开发语言·人工智能·python·大模型·ai产品经理·大模型学习·大模型入门
Pandaconda19 分钟前
【Golang 面试题】每日 3 题(三十九)
开发语言·经验分享·笔记·后端·面试·golang·go
半盏茶香20 分钟前
扬帆数据结构算法之雅舟航程,漫步C++幽谷——LeetCode刷题之移除链表元素、反转链表、找中间节点、合并有序链表、链表的回文结构
数据结构·c++·算法
加油,旭杏23 分钟前
【go语言】变量和常量
服务器·开发语言·golang
行路见知23 分钟前
3.3 Go 返回值详解
开发语言·golang
xcLeigh27 分钟前
WPF实战案例 | C# WPF实现大学选课系统
开发语言·c#·wpf
哎呦,帅小伙哦28 分钟前
Effective C++ 规则41:了解隐式接口和编译期多态
c++·effective c++
NoneCoder37 分钟前
JavaScript系列(38)-- WebRTC技术详解
开发语言·javascript·webrtc
关关钧1 小时前
【R语言】数学运算
开发语言·r语言