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.rhkb.cn/news/12118.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OPENGLPG第九版学习 - 着色器基础

文章目录 2.1 着色器与OpenGL2.2 0penGL的可编程管线2.3 OpenGL着色语言GLSL概述2.3.1 使用GLSL构建着色器变量的声明变量的作用域变量的初始化构造函数 、 类型转换聚合类型访问向量和矩阵中的元素结构体数组多维数组 2.3.2 存储限制符const 存储限制符in 存储限制符out 存储限…

路径规划之启发式算法之二十九:鸽群算法(Pigeon-inspired Optimization, PIO)

鸽群算法(Pigeon-inspired Optimization, PIO)是一种基于自然界中鸽子群体行为的智能优化算法,由Duan等人于2014年提出。该算法模拟了鸽子在飞行过程中利用地标、太阳和磁场等导航机制的行为,具有简单、高效和易于实现的特点,适用于解决连续优化问题。 更多的仿生群体算法…

Docker Compose的使用

文章首发于我的博客&#xff1a;https://blog.liuzijian.com/post/docker-compose.html 目录 Docker Compose是什么Docker Compose安装Docker Compose文件Docker Compose常用命令案例&#xff1a;部署WordPress博客系统 Docker Compose是什么 Docker Compose是Docker官方的开源…

AP单类平均准确率

P_true N_true P_pred TP Fp N_pred FN TNP NTP&#xff08;真正样本&#xff0c;与真实框IoU大于阈值的框&#xff09; FP&#xff08;假正样本&#xff0c;与真实框IoU小于阈值的框&#xff09; TN&#xff08;真负样本&#xff0c;背景&#xff09;…

Leetcode—1427. 字符串的左右移【简单】Plus

2025每日刷题&#xff08;206&#xff09; Leetcode—1427. 字符串的左右移 实现代码 class Solution { public:string stringShift(string s, vector<vector<int>>& shift) {// shift[i] [dir, amount]// dir 0(左) or 1(右)// 左表示正, 右表示负int len…

机器学习10

自定义数据集 使用scikit-learn中svm的包实现svm分类 代码 import numpy as np import matplotlib.pyplot as pltclass1_points np.array([[1.9, 1.2],[1.5, 2.1],[1.9, 0.5],[1.5, 0.9],[0.9, 1.2],[1.1, 1.7],[1.4, 1.1]])class2_points np.array([[3.2, 3.2],[3.7, 2.9],…

股票入门知识

股票入门&#xff08;更适合中国宝宝体制&#xff09; 股市基础知识 本文介绍了股票的基础知识&#xff0c;股票的分类&#xff0c;各板块发行上市条件&#xff0c;股票代码&#xff0c;交易时间&#xff0c;交易规则&#xff0c;炒股术语&#xff0c;影响股价的因素&#xf…

Golang 并发机制-3:通道(channels)机制详解

并发编程是一种创建性能优化且响应迅速的软件的强大方法。Golang&#xff08;也称为 Go&#xff09;通过通道&#xff08;channels&#xff09;这一特性&#xff0c;能够可靠且优雅地实现并发通信。本文将揭示通道的概念&#xff0c;解释其在并发编程中的作用&#xff0c;并提供…

C#,入门教程(11)——枚举(Enum)的基础知识和高级应用

上一篇&#xff1a; C#&#xff0c;入门教程(10)——常量、变量与命名规则的基础知识https://blog.csdn.net/beijinghorn/article/details/123913570 不会枚举&#xff0c;就不会编程&#xff01; 枚举 一个有组织的常量系列 比如&#xff1a;一个星期每一天的名字&#xf…

读书笔记--分布式架构的异步化和缓存技术原理及应用场景

本篇是在上一篇的基础上&#xff0c;主要对分布式应用架构下的异步化机制和缓存技术进行学习&#xff0c;主要记录和思考如下&#xff0c;供大家学习参考。大家知道原来传统的单一WAR应用中&#xff0c;由于所有数据都在同一个数据库中&#xff0c;因此事务问题一般借助数据库事…

【C++】继承(下)

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解C的继承&#xff08;下&#xff09;&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 5.继承与友元6.继承与静态成员7.复杂的菱形继承及菱形虚拟继承8.继…

基于LLM的路由在专家混合应用:一种新颖的交易框架,该框架在夏普比率和总回报方面提升了超过25%

“LLM-Based Routing in Mixture of Experts: A Novel Framework for Trading” 论文地址&#xff1a;https://arxiv.org/pdf/2501.09636 摘要 随着深度学习和大语言模型&#xff08;LLMs&#xff09;的不断进步&#xff0c;混合专家&#xff08;MoE&#xff09;机制在股票投资…

Med-R2:基于循证医学的检索推理框架:提升大语言模型医疗问答能力的新方法

Med-R2 : Crafting Trustworthy LLM Physicians through Retrieval and Reasoning of Evidence-Based Medicine Med-R2框架Why - 这个研究要解决什么现实问题What - 核心发现或论点是什么How - 1. 前人研究的局限性How - 2. 你的创新方法/视角How - 3. 关键数据支持How - 4. 可…

【Blazor学习笔记】.NET Blazor学习笔记

我是大标题 我学习Blazor的顺序是基于Blazor University&#xff0c;然后实际内容不完全基于它&#xff0c;因为它的例子还是基于.NET Core 3.1做的&#xff0c;距离现在很遥远了。 截至本文撰写的时间&#xff0c;2025年&#xff0c;最新的.NET是.NET9了都&#xff0c;可能1…

C++ Primer 迭代器

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

2 [GitHub遭遇严重供应链投毒攻击]

近日&#xff0c;有黑客针对 Discord Top.gg 的GitHub 账户发起了供应链攻击&#xff0c;此次攻击导致账户密码、凭证和其他敏感信息被盗&#xff0c;同时也影响到了大量开发人员。 Checkmarx 在一份技术报告中提到&#xff0c;黑客在这次攻击中使用了多种TTP&#xff0c;其中…

【AudioClassificationModelZoo-Pytorch】基于Pytorch的声音事件检测分类系统

源码&#xff1a;https://github.com/Shybert-AI/AudioClassificationModelZoo-Pytorch 模型测试表 模型网络结构batch_sizeFLOPs(G)Params(M)特征提取方式数据集类别数量模型验证集性能EcapaTdnn1280.486.1melUrbanSound8K10accuracy0.974, precision0.972 recall0.967, F1-s…

基于Spring Security 6的OAuth2 系列之七 - 授权服务器--自定义数据库客户端信息

之所以想写这一系列&#xff0c;是因为之前工作过程中使用Spring Security OAuth2搭建了网关和授权服务器&#xff0c;但当时基于spring-boot 2.3.x&#xff0c;其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0&#xff0c;结果一看Spring Security也升级…

Jupyterlab和notebook修改文件的默认存放路径的方法

文章目录 1.缘由2.操作流程2.1找到默认的路径2.2创建配置文件2.3修改配置文件内容2.4注意事项 1.缘由 我自己使用jupyterlab的时候&#xff0c;打开是在这个浏览器上面打开的&#xff0c;但是这个打开的文件路径显示的是C盘上面路径&#xff0c;所以这个就很麻烦&#xff0c;因…

算法题(56):旋转链表

审题&#xff1a; 我们需要根据k的大小把链表向右移动对应次数&#xff0c;并返回移动后的链表的头结点指针 思路&#xff1a; 根据提示中的数据大小我们发现&#xff1a;k的值可以远大于节点数。 也就是说我们对链表的操作存在周期&#xff0c;如果k%len0&#xff0c;说明我们…