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

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


一、概述

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

  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. 生产者-消费者模型的实现

    • 使用共享队列进行数据传递。
    • 条件变量协调线程之间的依赖顺序。
相关推荐
小王爱吃月亮糖5 分钟前
C++进阶-1-单继承、多继承、虚继承
开发语言·c++·笔记·学习·visual studio
m0_6075487625 分钟前
什么是单例模式
开发语言·javascript·单例模式
Am心若依旧40926 分钟前
[c++进阶(三)]单例模式及特殊类的设计
java·c++·单例模式
小王爱吃月亮糖27 分钟前
补充--C++的项目结构和管理
数据结构·c++·笔记·学习
因特麦克斯28 分钟前
如何实现对象的克隆?如何实现单例模式?
c++·单例模式
檀越剑指大厂2 小时前
【Python系列】Python中的`any`函数:检查“至少有一个”条件满足
开发语言·python
Crazy learner2 小时前
C 和 C++ 动态库的跨语言调用原理
c语言·c++
I_Am_Me_2 小时前
【JavaEE初阶】线程安全问题
开发语言·python
运维&陈同学3 小时前
【Elasticsearch05】企业级日志分析系统ELK之集群工作原理
运维·开发语言·后端·python·elasticsearch·自动化·jenkins·哈希算法
金士顿5 小时前
MFC 文档模板 每个文档模板需要实例化吧
c++·mfc