C++一分钟之-互斥锁与条件变量

本文涉及的产品
实时计算 Flink 版,1000CU*H 3个月
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
实时数仓Hologres,5000CU*H 100GB 3个月
简介: 【6月更文挑战第26天】在C++并发编程中,`std::mutex`提供互斥访问,防止数据竞争,而`std::condition_variable`用于线程间的同步协调。通过`lock_guard`和`unique_lock`防止忘记解锁,避免死锁。条件变量需配合锁使用,确保在正确条件下唤醒线程,注意虚假唤醒和无条件通知。生产者-消费者模型展示了它们的应用。正确使用这些工具能解决同步问题,提升并发性能和可靠性。

在C++并发编程中,同步机制是保证数据一致性与线程安全的重要工具。std::mutex(互斥锁)提供了基本的互斥访问保护,而std::condition_variable(条件变量)则用于线程间的精确协调,让线程在满足特定条件时才继续执行。本文将深入浅出地讲解这两者的使用、常见问题、易错点以及如何避免这些问题,并通过实例代码加深理解。
image.png

一、互斥锁(std::mutex)

互斥锁是实现线程间资源独占访问的基础手段。一旦一个线程获得了锁,其他试图获取同一锁的线程将会被阻塞,直到锁被释放。

基本用法

std::mutex mtx;
// 加锁
mtx.lock();
// 执行临界区代码
// ...
// 解锁
mtx.unlock();

易错点与避免策略

  1. 忘记解锁:使用std::lock_guardstd::unique_lock自动管理锁的生命周期,确保即使发生异常也能解锁。
  2. 死锁:避免在持有锁的情况下调用可能阻塞的函数,或按相同的顺序获取多个锁。

二、条件变量(std::condition_variable)

条件变量用于线程间同步,允许一个线程等待(挂起)直到另一个线程通知某个条件为真。

基本用法

std::condition_variable cv;
std::mutex mtx;

void waitingFunction() {
   
   
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{
   
   return conditionToWaitFor;}); // 条件满足前挂起
    // 条件满足后执行的代码
}

void notifyingFunction() {
   
   
    // 修改状态使得conditionToWaitFor为真
    std::lock_guard<std::mutex> lock(mtx);
    cv.notify_one(); // 唤醒一个等待的线程
}

常见问题与避免策略

  1. 无条件唤醒:不要在没有改变条件的情况下调用notify_*函数,这可能导致不必要的线程唤醒和重新检查条件。
  2. 虚假唤醒:即使没有调用notify_*,等待的线程也可能被唤醒。因此,总是使用条件来检查是否真正满足继续执行的条件。
  3. 死锁:确保在调用wait之前已经获得了锁,并且在wait之后立即检查条件,避免在持有锁的情况下执行耗时操作。

三、综合示例:生产者-消费者模型

#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>

std::queue<int> producedItems;
std::mutex mtx;
std::condition_variable condVar;

bool doneProducing = false;

void producer(int n) {
   
   
    for (int i = 0; i < n; ++i) {
   
   
        std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟生产时间
        std::lock_guard<std::mutex> lock(mtx);
        producedItems.push(i);
        condVar.notify_one(); // 通知消费者
        if (i == n - 1) doneProducing = true;
    }
}

void consumer() {
   
   
    while (true) {
   
   
        std::unique_lock<std::mutex> lock(mtx);
        condVar.wait(lock, []{
   
   return !producedItems.empty() || doneProducing;});
        if (!producedItems.empty()) {
   
   
            int item = producedItems.front();
            producedItems.pop();
            std::cout << "Consumed: " << item << std::endl;
        } else if (doneProducing) {
   
   
            break;
        }
    }
}

int main() {
   
   
    std::thread producerThread(producer, 10);
    std::thread consumerThread(consumer);

    producerThread.join();
    consumerThread.join();

    return 0;
}

四、总结

互斥锁和条件变量是构建复杂并发系统不可或缺的组件。正确使用它们,可以有效解决线程间的同步问题,避免数据竞争和死锁。实践中,应注重细节,如使用RAII模式管理锁的生命周期、仔细设计条件判断逻辑,以及避免无意义的线程唤醒。通过上述示例和策略的学习,希望你能更加自信地在C++项目中应用这些并发工具,提升程序的并发性能和可靠性。随着经验的积累,逐步探索更高级的并发模式和库,如C++20中的std::latchstd::barrier,将使你的并发编程技能更加全面和高效。

目录
相关文章
|
4月前
|
存储 C++
C++语言中指针变量int和取值操作ptr详细说明。
总结起来,在 C++ 中正确理解和运用 int 类型地址及其相关取值、设定等操纵至关重要且基础性强:定义 int 类型 pointer 需加星号;初始化 pointer 需配合 & 取址;读写 pointer 执向之处需配合 * 解引用操纵进行。
432 12
|
Linux API C++
超级好用的C++实用库之互斥锁
超级好用的C++实用库之互斥锁
133 2
|
JavaScript 前端开发 Java
通过Gtest访问C++静态、私有、保护变量和方法
通过Gtest访问C++静态、私有、保护变量和方法
348 1
|
存储 安全 C++
C++:指针引用普通变量适用场景
指针和引用都是C++提供的强大工具,它们在不同的场景下发挥着不可或缺的作用。了解两者的特点及适用场景,可以帮助开发者编写出更加高效、可读性更强的代码。在实际开发中,合理选择使用指针或引用是提高编程技巧的关键。
164 1
|
C语言 C++
实现两个变量值的互换[C语言和C++的区别]
实现两个变量值的互换[C语言和C++的区别]
188 0
|
C++ 运维
开发与运维编译问题之在C++中在使用std::mutex后能自动释放锁如何解决
开发与运维编译问题之在C++中在使用std::mutex后能自动释放锁如何解决
237 2
|
程序员 编译器 C++
探索C++语言宝库:解锁基础知识与实用技能(类型变量+条件循环+函数模块+OOP+异常处理)
探索C++语言宝库:解锁基础知识与实用技能(类型变量+条件循环+函数模块+OOP+异常处理)
121 0
|
10月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
6月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
174 0
|
6月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
266 0