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

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


一、概述

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

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

二、线程间的互斥

1. 静态条件与临界区

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

2. 互斥锁(Mutex)

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

三、线程间的同步通信

1. 同步通信的必要性

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

2. 条件变量(Condition Variable)

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

四、生产者-消费者模型

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. 线程安全:

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

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

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

五、总结

  1. 互斥锁的作用:

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

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

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

    • 使用共享队列进行数据传递。
    • 条件变量协调线程之间的依赖顺序。
相关推荐
h39744 小时前
DirectShow过滤器开发-视频源过滤器
c++·windows·音视频·directshow
夏幻灵5 小时前
JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱
开发语言·javascript·面试
程序猿编码5 小时前
榨干 RTX5090 算力!Qwen3 专用单卡推理引擎,手写C++/CUDA 算子实现 MTP 推测解码
开发语言·c++·大模型推理·qwen3·多模态推理
西飘客6 小时前
vs 怎么根据dll 生成lib文件
c++·qt
bkspiderx7 小时前
Qt 插件机制:动态扩展应用功能的核心框架
开发语言·qt·元数据·qt 插件·qpluginloader
程序员老陆7 小时前
深入理解 C++ thread_local:线程私有存储的正确打开方式
开发语言·c++·程序设计
沐晓时光7 小时前
C语言入门,深入理解指针(3)
c语言·开发语言
码云数智-园园7 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言
禾小西8 小时前
07丨Redis 哨兵机制:主库故障后,如何恢复服务?
java·开发语言·redis
我不是阵雨8 小时前
JDK 21虚拟线程Pinning陷阱:一文拔钉解困
java·开发语言