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

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


一、概述

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

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

    • 使用共享队列进行数据传递。
    • 条件变量协调线程之间的依赖顺序。
相关推荐
秋风&萧瑟11 分钟前
【QT】QT的多界面跳转以及界面之间传递参数
开发语言·qt
骑牛小道士13 分钟前
JAVA- 锁机制介绍 进程锁
java·开发语言
郭涤生16 分钟前
Chapter 1: Historical Context_《C++20Get the details》_notes
开发语言·c++20
独好紫罗兰28 分钟前
洛谷题单2-P5712 【深基3.例4】Apples-python-流程图重构
开发语言·python·算法
东方佑43 分钟前
深度解析Python-PPTX库:逐层解析PPT内容与实战技巧
开发语言·python·powerpoint
水w1 小时前
【Android Studio】如何卸载干净(详细步骤)
android·开发语言·android studio·activity
weixin_307779131 小时前
判断HiveQL语句为建表语句的识别函数
开发语言·数据仓库·hive·c#
一顿操作猛如虎,啥也不是!1 小时前
JAVA-Spring Boot多线程
开发语言·python
v维焓1 小时前
C++(思维导图更新)
开发语言·c++·算法
jiet_h1 小时前
深入解析KSP(Kotlin Symbol Processing):现代Android开发的新利器
android·开发语言·kotlin