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

C++ 条件变量,互斥锁

C++ 中多线程编程的两个核心同步原语:互斥锁 (Mutex)条件变量 (Condition Variable)。它们是实现线程间安全通信和协调的关键。

1. 互斥锁 (Mutex)

核心概念

互斥锁用于保护共享数据,确保同一时间只有一个线程可以访问该数据,从而避免数据竞争 (Data Race)

原理:线程在访问共享数据前先上锁 (lock),如果锁已被其他线程占用,则当前线程阻塞 (block) 等待。访问完成后解锁 (unlock),让其他线程有机会获取锁。

C++ 中的互斥锁 (<mutex> 头文件)

1. std::mutex

最基本的互斥锁。

cpp

#include <iostream>
#include <thread>
#include <mutex>std::mutex g_mutex; // 全局互斥锁
int shared_data = 0;void increment() {for (int i = 0; i < 100000; ++i) {g_mutex.lock();    // 上锁++shared_data;     // 临界区代码g_mutex.unlock();  // 解锁}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final value: " << shared_data << std::endl; // 正确输出 200000return 0;
}
2. std::lock_guard (推荐使用)

RAII 风格的锁管理,在构造时自动上锁,析构时自动解锁,即使发生异常也能保证解锁,避免死锁。

cpp

void safe_increment() {for (int i = 0; i < 100000; ++i) {std::lock_guard<std::mutex> lock(g_mutex); // 构造时上锁,析构时解锁++shared_data;} // lock_guard 在此析构,自动解锁
}
3. std::unique_lock (更灵活)

lock_guard 更灵活,可以手动控制上锁和解锁的时机,是条件变量必需的伙伴。

cpp

void flexible_increment() {for (int i = 0; i < 100000; ++i) {std::unique_lock<std::mutex> lock(g_mutex); // 自动上锁++shared_data;// 可以手动提前解锁,不需要等到作用域结束lock.unlock();// ... 这里可以执行一些不涉及共享数据的操作}
}

2. 条件变量 (Condition Variable)

核心概念

条件变量用于线程间的通信和协调。它允许一个线程等待某个条件成立,而其他线程在条件成立时通知等待的线程。

典型生产者-消费者模式

  • 消费者线程等待"缓冲区不为空"的条件

  • 生产者线程在放入数据后通知消费者

C++ 中的条件变量 (<condition_variable> 头文件)

基本使用模式

cpp

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
const int MAX_SIZE = 5;// 生产者线程
void producer() {for (int i = 0; i < 10; ++i) {std::unique_lock<std::mutex> lock(mtx);// 等待条件:队列未满// 使用lambda表达式作为等待条件cv.wait(lock, [] { return data_queue.size() < MAX_SIZE; });data_queue.push(i);std::cout << "Produced: " << i << std::endl;lock.unlock(); // 手动解锁(可选,notify之前解锁更好)cv.notify_one(); // 通知一个等待的消费者}
}// 消费者线程
void consumer() {while (true) {std::unique_lock<std::mutex> lock(mtx);// 等待条件:队列不为空cv.wait(lock, [] { return !data_queue.empty(); });int data = data_queue.front();data_queue.pop();std::cout << "Consumed: " << data << std::endl;lock.unlock();cv.notify_one(); // 通知生产者可能有空位了if (data == 9) break; // 收到最后一个数据后退出}
}int main() {std::thread prod(producer);std::thread cons(consumer);prod.join();cons.join();return 0;
}

条件变量的关键方法

  1. wait(lock, predicate):

    • 原子地解锁并阻塞当前线程

    • 被唤醒后重新获取锁

    • 检查 predicate 条件,如果为 false 则继续等待

  2. notify_one():

    • 唤醒一个等待中的线程(如果有)

  3. notify_all():

    • 唤醒所有等待中的线程


3. 为什么条件变量需要互斥锁?

条件变量必须与互斥锁配合使用,原因如下:

  1. 原子性操作:检查条件和进入等待必须是原子操作,否则可能发生:

    • 线程A检查条件 → 条件不满足

    • 线程B修改条件并发出通知

    • 线程A才开始等待 → 通知丢失,线程A永远等待

  2. 保护共享状态:条件变量等待的"条件"通常是共享数据,需要用互斥锁保护。


4. 完整的生产者-消费者示例

cpp

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>class ThreadSafeQueue {
private:std::queue<int> queue_;std::mutex mtx_;std::condition_variable cv_producer_;std::condition_variable cv_consumer_;const int max_size_ = 5;bool stop_ = false;public:void push(int value) {std::unique_lock<std::mutex> lock(mtx_);cv_producer_.wait(lock, [this] { return queue_.size() < max_size_ || stop_; });if (stop_) return;queue_.push(value);std::cout << "Produced: " << value << std::endl;cv_consumer_.notify_one();}int pop() {std::unique_lock<std::mutex> lock(mtx_);cv_consumer_.wait(lock, [this] { return !queue_.empty() || stop_; });if (stop_ && queue_.empty()) return -1;int value = queue_.front();queue_.pop();std::cout << "Consumed: " << value << std::endl;cv_producer_.notify_one();return value;}void stop() {std::lock_guard<std::mutex> lock(mtx_);stop_ = true;cv_producer_.notify_all();cv_consumer_.notify_all();}
};int main() {ThreadSafeQueue queue;std::thread producer([&queue] {for (int i = 0; i < 10; ++i) {queue.push(i);std::this_thread::sleep_for(std::chrono::milliseconds(100));}queue.stop();});std::thread consumer([&queue] {while (true) {int value = queue.pop();if (value == -1) break;std::this_thread::sleep_for(std::chrono::milliseconds(150));}});producer.join();consumer.join();return 0;
}

5. 重要注意事项和最佳实践

  1. 虚假唤醒 (Spurious Wakeup):线程可能在没有收到通知的情况下被唤醒,因此必须使用谓词检查条件。

  2. 优先使用 std::lock_guardstd::unique_lock:避免手动调用 lock()/unlock()

  3. 在通知前解锁:在调用 notify_one()notify_all() 前解锁,可以让被唤醒的线程立即获取锁,提高性能。

  4. 使用 RAII:确保异常安全,所有资源都能正确释放。

  5. 避免嵌套锁:容易导致死锁。

总结对比

特性互斥锁 (Mutex)条件变量 (Condition Variable)
主要目的保护共享数据,避免数据竞争线程间通信和协调
操作lock(), unlock()wait(), notify_one(), notify_all()
配合使用可以单独使用必须与互斥锁配合使用
阻塞原因等待获取锁等待某个条件成立
典型模式临界区保护生产者-消费者

掌握互斥锁和条件变量是编写正确、高效多线程 C++ 程序的基础。

http://www.xdnf.cn/news/1437787.html

相关文章:

  • 【c++】多态+RTTI (运行时的类型识别信息)
  • 深度学习篇---DenseNet
  • 深入解析Linux进程概念与操作系统核心
  • 深度学习篇---SGD优化器
  • 「数据获取」《安徽建设统计年鉴》(2002-2007)(2004、2006缺失)(获取方式看绑定的资源)
  • spring boot驴友结伴游网站的设计与实现(代码+数据库+LW)
  • 使用Global Watersheds提取水文站控制区域(水文站上下游 流域水系等)
  • 【自记】Python 中 简化装饰器使用的便捷写法语法糖(Syntactic Sugar)示例
  • 复刻 Python 实现的小智语音客户端项目py-xiaozhi日记
  • 【算法笔记 day six】二分算法的第三部分
  • 手写Muduo网络库核心代码1-- noncopyable、Timestamp、InetAddress、Channel 最详细讲解
  • 测试覆盖率不够高?这些技巧让你的FastAPI测试无懈可击!
  • maven【maven】技术详解
  • ARM编译器生成的AXF文件解析
  • 平衡车-ADC采集电池电压
  • 综合诊断板CAN时间戳稳定性测试报告8.28
  • Linux内核进程管理子系统有什么第四十回 —— 进程主结构详解(36)
  • 安装部署k3s
  • Java试题-选择题(29)
  • 算法题打卡力扣第3题:无重复字符的最长子串(mid)
  • Suno AI 新功能上线:照片也能唱歌啦!
  • Netty从0到1系列之NIO
  • 进程优先级(Process Priority)
  • 猫猫狐狐的“你今天有点怪怪的”侦察日记
  • CentOS7安装Nginx服务——为你的网站配置https协议和自定义服务端口
  • Java注解深度解析:从@ResponseStatus看注解奥秘
  • 大模型RAG项目实战:Pinecone向量数据库代码实践
  • 二叉树经典题目详解(下)
  • 【数据分享】31 省、342 个地级市、2532 个区县农业机械总动力面板数据(2000 - 2020)
  • MySQL数据库——概述及最基本的使用