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

C++11 多线程 锁与条件变量:mutex、lock_guard、unique_lock 和 condition_variable

文章目录

  • mutex
    • 核心成员函数
    • 使用场景
  • lock_guard
    • 功能和特性
    • 构造函数
    • 使用场景
  • unique_lock
    • 功能和特性
    • 构造函数
    • 核心成员函数
    • 使用场景
  • lock_guard对比unique_lock
  • condition_variable
    • 核心成员函数
    • 使用场景

mutex

std::mutex 是 C++ 标准库中提供的一种互斥量,用于在多线程环境下同步线程对共享资源的访问。通过互斥量,可以确保某一时刻只有一个线程能够访问或修改共享资源,从而避免数据竞争。

核心成员函数

  • lock():用于获取互斥量的锁。若互斥量当前未被锁定,调用线程会立即获得锁并继续执行;若互斥量已被其他线程锁定,调用线程会被阻塞,直到锁被释放并成功获取。

  • try_lock():尝试获取互斥量的锁,但不会阻塞线程。若互斥量当前未被锁定,调用线程会获得锁并返回true;若互斥量已被其他线程锁定,函数会立即返回false

  • unlock():释放互斥量的锁,允许其他线程获取该锁。调用此函数的线程必须是当前持有锁的线程,否则会导致未定义行为。

使用场景

  • 保护共享资源:当多个线程需要访问和修改同一个共享资源时,使用std::mutex可以确保在任何时刻只有一个线程能对资源进行操作,避免数据不一致的问题。例如,多个线程同时对一个全局变量进行递增操作,就需要用std::mutex保护该变量。
std::mutex mtx;
int counter = 0;

void incrementCounter() {
    for (int i = 0; i < 1000; ++i) {
        // 获取互斥锁
        mtx.lock();
        // 对共享计数器进行递增
        ++counter;
        // 释放互斥锁
        mtx.unlock();
    }
}

lock_guard

std::lock_guard 是 C++ 标准库中的一个 RAII(Resource Acquisition Is Initialization)锁管理器,用于简化互斥量的加锁和解锁操作。它在构造时自动加锁互斥量,在作用域结束时自动解锁,从而确保互斥量能够正确释放,避免因为异常或逻辑错误导致的死锁或资源泄漏问题。

功能和特性

  • 自动管理锁的生命周期std::lock_guard 在构造时会自动锁定互斥量,在其作用域结束时(即对象被销毁时),会自动调用互斥量的 unlock 方法解锁,无需手动调用 lockunlock,避免了因忘记解锁而导致的死锁问题。
  • 独占所有权:同一时间只有一个 std::lock_guard 对象可以拥有互斥量的所有权,确保对共享资源的独占访问,防止多线程并发访问时的数据竞争。
  • 不可复制std::lock_guard 的拷贝构造函数和拷贝赋值运算符被删除,不允许复制,因为复制可能会导致多个对象同时管理同一个互斥量,引发未定义行为。

构造函数

  • explicit lock_guard(mutex_type& m);:创建一个 std::lock_guard 对象,并立即锁定互斥量 mexplicit 关键字用于防止隐式类型转换,确保只能显式调用该构造函数。
  • lock_guard(mutex_type& m, adopt_lock_t t);:假设当前线程已经锁定了互斥量 m,创建 std::lock_guard 对象来管理该互斥量,不会再次尝试锁定。adopt_lock_t 是一个空的标记类型,用于区分不同的构造方式。

使用场景

  • 简单的临界区保护:当需要保护一个简单的代码块,确保同一时间只有一个线程可以执行该代码块时,std::lock_guard 是一个很好的选择。例如,对共享变量的读写操作、对共享数据结构的修改等。
std::mutex mtx;
int counter = 0;

void incrementCounter() {
    for (int i = 0; i < 1000; ++i) {
        // 创建 std::lock_guard 对象,构造时自动锁定互斥量
        std::lock_guard<std::mutex> lock(mtx);
        // 对共享计数器进行递增
        ++counter;
        // 离开此作用域,std::lock_guard 对象析构,自动解锁互斥量
    }
}

unique_lock

std::unique_lock 是 C++ 标准库中提供的一种线程同步工具,位于 <mutex> 头文件中。它是一个智能锁,提供了更灵活的互斥锁管理方式,与 std::lock_guard 相比,功能更强大。

功能和特性

  • 独占所有权:同一时间内,std::unique_lock保证只有一个实例能拥有互斥量,这避免了多线程环境下对共享资源的并发访问问题,确保线程安全。
  • 灵活锁定:在创建时不强制立即锁定互斥量,也可以在生命周期内随时锁定或解锁,比std::lock_guard更具灵活性。
  • 可移动性std::unique_lock支持移动语义,允许在不同作用域和函数间转移互斥量的所有权。

构造函数

  • unique_lock(mutex_type& m):创建对象并立即锁定互斥量m,若互斥量被其他线程占用,当前线程会阻塞。
  • unique_lock(mutex_type& m, std::defer_lock_t t):创建对象但不锁定互斥量,后续需手动调用lock()锁定。

核心成员函数

  • lock():锁定关联的互斥量,若被其他线程占用则阻塞。

  • unlock():解锁关联的互斥量。

  • try_lock():尝试锁定互斥量,成功返回true,失败返回false,不阻塞。

  • owns_lock():检查对象是否拥有互斥量的所有权。

  • release():释放互斥量的所有权,返回指向互斥量的指针。

  • mutex():返回管理的互斥量指针。

使用场景

  • 需要灵活锁定和解锁的场景:当在临界区中需要根据特定条件锁定或解锁互斥量时,std::unique_lock非常有用。
std::mutex mtx;
int sharedData = 0;

void complexTask() {
    // 创建 std::unique_lock 对象,但不立即锁定互斥锁
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
    // ...

    // 根据条件决定是否锁定
    if (true) {
        lock.lock();
        // 临界区
        sharedData++;
        std::cout << "Shared resource value: " << sharedData << std::endl;
        // 提前解锁
        lock.unlock();
    }

    // ...
}
  • 与条件变量配合使用std::condition_variablewait系列函数要求使用std::unique_lock,因为这些函数在等待期间需要解锁和重新锁定互斥量。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

// 等待特定条件达成,继续工作
void worker() {
    std::unique_lock<std::mutex> lock(mtx);
    // 等待条件变量通知
    cv.wait(lock, [] { return ready; });
    std::cout << "Worker thread is working..." << std::endl;
}

// 设置条件变量并通知等待的线程
void setReady() {
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    // 通知等待的线程
    cv.notify_one();
}

int main() {
    std::thread t1(worker);
    std::thread t2(setReady);
    t1.join();
    t2.join();
    return 0;
}

lock_guard对比unique_lock

特性std::lock_guardstd::unique_lock
自动加锁
延迟锁定
尝试锁定
手动解锁
接管已锁定的互斥量支持支持
开销较低较高
  • std::lock_guard 是 C++ 提供的简单、高效的锁管理工具,非常适合短时间的互斥量保护场景。通过 RAII 机制,std::lock_guard 大大减少了因手动管理锁而可能导致的错误。
  • std::unique_lock可以应对更复杂的锁管理需求(如延迟锁定或多次解锁/加锁)。

condition_variable

条件变量 (std::condition_variable) 是 C++ 标准库中提供的一种线程同步工具,用于线程之间的高效通信和协调。它允许一个线程等待某个条件满足时再继续执行,同时其他线程可以通知(signal)它条件已经满足。

核心成员函数

  • wait:该函数用于使当前线程阻塞,直到收到通知且满足特定条件。它有两种重载形式:
    • void wait(std::unique_lock<std::mutex>& lock);:该形式会释放当前线程持有的互斥锁lock,并使线程进入阻塞状态,直到收到其他线程的通知。当线程被唤醒时,它会重新获取互斥锁。
    • template< class Predicate > void wait(std::unique_lock<std::mutex>& lock, Predicate pred);:这种形式会在每次被唤醒时检查pred条件,如果条件不满足,则继续等待;只有当条件满足时,线程才会继续执行。
  • wait_for:与wait类似,但它允许指定一个超时时间。如果在超时时间内没有收到通知或条件未满足,线程会自动唤醒。
  • notify_one:唤醒一个等待在该条件变量上的线程。如果有多个线程在等待,只会唤醒其中一个,具体唤醒哪个线程是不确定的。
  • notify_all:唤醒所有等待在该条件变量上的线程。

使用场景

  • 生产者-消费者模型:生产者线程负责生产数据并将其放入缓冲区,消费者线程则从缓冲区取出数据进行处理。当缓冲区为空时,消费者线程需要等待;当缓冲区满时,生产者线程需要等待。
std::queue<int> buffer;
std::mutex mtx;
std::condition_variable not_full, not_empty;
const int MAX_SIZE = 5;

void producer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        // 等待缓冲区不满
        not_full.wait(lock, [] { return buffer.size() < MAX_SIZE; });
        buffer.push(i);
        std::cout << "Produced: " << i << std::endl;
        // 通知消费者缓冲区非空
        not_empty.notify_one();
    }
}

void consumer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        // 等待缓冲区非空
        not_empty.wait(lock, [] { return !buffer.empty(); });
        int item = buffer.front();
        buffer.pop();
        std::cout << "Consumed: " << item << std::endl;
        // 通知生产者缓冲区非满
        not_full.notify_one();
    }
}

多线程任务同步:在某些场景中,一个或多个线程需要等待其他线程完成特定任务后才能继续执行。条件变量可以用来实现这种同步机制。

线程池任务调度:在线程池中,工作线程需要等待新任务的到来。当有新任务提交时,主线程可以通知一个或多个工作线程开始执行任务。

资源分配与使用:当多个线程竞争有限的资源时,条件变量可以用于协调资源的分配和使用。例如,当资源可用时,等待的线程可以被唤醒使用资源;当资源被占用时,线程需要等待。


http://www.kler.cn/a/531212.html

相关文章:

  • DeepSeek超越ChatGPT的能力及部分核心原理
  • Node.js常用知识
  • 网站快速收录:如何优化网站音频内容?
  • Node 服务器数据响应类型处理
  • Vue3 表单:全面解析与最佳实践
  • Spring Boot 实例解析:配置文件
  • 电控三周速成计划参考
  • 51c嵌入式~电路~合集25
  • GRE阅读双线阅读 --青山学堂GRE全程班 包括 阅读、数学、写作、填空、背单词
  • Math数字类
  • CH340G上传程序到ESP8266-01(S)模块
  • 大模型领域的Scaling Law的含义及作用
  • 7-4 西安距离
  • SAP HCM 回溯分析
  • 民法学学习笔记(个人向) Part.2
  • 元音字母(模拟)
  • 网络工程师 (16)侵权判断
  • DeepSeek- R1 原理介绍
  • Java中的泛型及其用途是什么?
  • windows linux常用基础命令
  • C++类定义中的关键字public 、protected 、private的详细介绍【定义类成员的访问权限属性和基类的成员的访问权限属性】
  • python:求解爱因斯坦场方程
  • Nginx 变量集合
  • 从零开始部署Dify:后端与前端服务完整指南
  • CSS Module 常用笔记
  • ubuntuCUDA安装