C++ 线程库
文章目录
- thread 创建
- mutex
- mutex
- recursive_mutex
- timed_mutex
- lock_guard
- 原子操作
- atomic
- 条件变量
- condition_variable
- 其他线程安全问题
- shared_ptr
- 单例模式
C++ 线程库是 C++11 标准中引入的一个特性,它使得 C++ 在语言级别上支持多线程编程,不需要依赖第三方库或操作系统 API。C++ 线程库主要包括以下几个部分:
std::thread:创建和管理子线程的类std::mutex:互斥锁,用于保护共享数据的访问std::condition_variable:条件变量,用于同步线程之间的状态std::future和std::promise:异步任务和结果的封装std::async:启动异步任务的函数
thread 创建
thread
std::thread 类的构造函数:

std::thread 是一个类,它表示一个可执行的线程。你可以用它来创建和管理子线程,让它们并发地执行不同的任务。std::thread 有以下几个特点:
- 它不可拷贝,只能移动。如上(3)(4)
- 它可以被 join 或 detach,join 表示等待线程结束,detach 表示让线程自行运行
- 它有一个唯一的标识符 std::thread::id,可以用来区分不同的线程

在命名空间 this_thread下有一个 get_id 函数,可以帮助我们获取线程 id
yield:可以使当前线程让出时间片
sleep_until:sleep 到特定时间点
sleep_for:sleep 一段时间
一个双线程循环打印的例子:
#include <iostream>
#include <thread>using namespace std;void print(int n) {for (int i = 0; i < n; ++i) {cout << this_thread::get_id() << ":" << i << endl; // 打印 [线程id]:ithis_thread::sleep_for(chrono::seconds(1)); // sleep 1 秒}
}int main() {thread t1(print, 100);thread t2(print, 100);t1.join();t2.join();return 0;
}
mutex
mutex
例一:给上面的代码加锁
#include <iostream>
#include <thread>
#include <mutex>using namespace std;mutex mtx; // 创建一把锁void print(int n) {mtx.lock(); // 加锁for (int i = 0; i < n; ++i) {cout << this_thread::get_id() << ":" << i << endl; // 打印 [线程id]:ithis_thread::sleep_for(chrono::milliseconds(100)); // sleep 100 ms}mtx.unlock(); // 解锁
}int main() {thread t1(print, 100);thread t2(print, 100);t1.join();t2.join();return 0;
}
例二:
如果锁是创建在局部,则要通过函数参数传入
由于锁不支持拷贝,所以必须通过引用传入,又由于可变模板参数会默认识别成传值,所以必须先使用 ref 来强制转换成引用。
#include <iostream>
#include <thread>
#include <mutex>using namespace std;void print(int n, mutex& mtx) {mtx.lock(); // 加锁for (int i = 0; i < n; ++i) {cout << this_thread::get_id() << ":" << i << endl; // 打印 [线程id]:ithis_thread::sleep_for(chrono::milliseconds(100)); // sleep 100 ms}mtx.unlock(); // 解锁
}int main() {mutex mtx; // 创建一把锁thread t1(print, 100, ref(mtx)); // 必须使用 ref 来传引用thread t2(print, 100, ref(mtx));t1.join();t2.join();return 0;
}
例三:
配合 vector 和 lambda 使用
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>using namespace std;int main() {mutex mtx; // 创建一把锁int x = 0, n = 10, m;cin >> m;vector<thread> v(m);for (int i = 0; i < m; ++i) {v[i] = thread([&]() {mtx.lock(); // 加锁for (int i = 0; i < n; ++i) {cout << this_thread::get_id() << ":" << i << endl; // 打印 [线程id]:ithis_thread::sleep_for(chrono::milliseconds(100)); // sleep 100 ms}mtx.unlock(); // 解锁});}for (auto& t : v) {t.join();}return 0;
}
recursive_mutex
如果在递归函数中使用普通的互斥锁会造成死锁,因为当一个线程执行到 unlock 之前就可能会递归调用自己,然后从头开始执行,当再次遇到 lock 时,由于之前没有执行 unlock,就会死锁。
而 recursive_mutex 可以防止递归造成的死锁,它在每次 lock 的时候会判断是不是当前线程,如果是,就不用阻塞,可以继续向下执行了。
timed_mutex
定时互斥锁,该类锁可以设置锁住的时间。
由它的两个成员实现 try_lock_for 和 try_lock_until.
到时间后我们不需要手动解锁,而是自动解锁。
lock_guard
智能锁,通过 RAII 技术实现。
它的实现类似于如下方法:
template<class Lock>
class LockGuard {
public:LockGuard(Lock& lk) : _lock(lk) {_lock.lock();}~LockGuard() {_lock.unlock();}
private:Lock& _lock;
};
例:
int main() {mutex mtx; // 创建一把锁atomic<int> x = 0;//int x = 0;int n = 100, m;cin >> m;vector<thread> v(m);for (int i = 0; i < m; ++i) {v[i] = thread([&]() {for (int i = 0; i < n; ++i) {lock_guard<mutex> lk(mtx); // 智能锁cout << this_thread::get_id() << ':' << i << endl;this_thread::sleep_for(chrono::microseconds(100));}});}for (auto& t : v) {t.join();}cout << x << endl;return 0;
}
注:unique_guard 比 lock_guard 多一个支持手动加锁的解锁的功能。
原子操作
atomic
我们知道,++ 操作不是线程安全的,要避免多线程同时修改造成的数据不一致的问题,可以通过加锁来解决。
但是互斥锁会产生上下文切换的开销,容易产生死锁。更好的解决方法是使用原子操作。
CAS (Compare & Set,或 Compare & Swap)是一种原子操作,也就是说它是一个不会被其他线程打断的操作。CAS 有三个操作数:内存值 V,旧的预期值 A,要修改的新值 B。CAS 的过程是这样的:先比较内存值 V 和旧的预期值 A 是否相等,如果相等,就用新值 B 替换内存值 V;如果不相等,就放弃操作或者重试。CAS 可以用于实现无锁算法,避免多线程同时修改同一数据时产生的数据不一致问题。
C++11 中的 atomic 类就是 CAS 的一种实现。
例:
#include <iostream>
#include <thread>
#include <vector>using namespace std;int main() {//int x = 0;atomic<int> x = 0;int n = 100000, m;cin >> m;vector<thread> v(m);for (int i = 0; i < m; ++i) {v[i] = thread([&]() {for (int i = 0; i < n; ++i) {++x; // 原子操作}});}for (auto& t : v) {t.join();}cout << x << endl;return 0;
}
条件变量
condition_variable
条件变量是一种同步原语,用于让线程在某个条件发生时才继续执行。 条件变量与互斥锁配合使用,让线程在等待条件时释放锁,从而避免竞争状态。条件变量提供了一种原子操作,即解锁并睡眠的操作,以及唤醒并加锁的操作。条件变量有两个动作:等待(wait)和通知(notify)。等待动作会让线程挂起,并释放已经持有的锁。通知动作会唤醒一个或多个等待的线程,并让它们重新获取锁。
在 C++11 中,你可以使用 std::condition_variable 类来创建和操作条件变量。你需要配合一个互斥锁(std::mutex)和一个谓词(std::function<bool()>)来使用条件变量。你可以调用条件变量的成员函数,如wait(),notify_one(),notify_all()等来实现线程间的同步。

predicate(2) 是增加了谓词的版本,pred 是一个 std::function<bool()>,它返回 false 则表示阻塞,返回 true 时解除阻塞。
例:创建两个线程,交替打印 0~100,如线程 t2 先打印 0,则下面必须是另一个线程 t1 打印 1,然后 t2 打印 2 …
这是一个运用条件变量解决的经典场景,我们可以让一个线程打印完一个数之后立马通知另一个线程,从而保证两个线程交替进行。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>using namespace std;int main() {int i = 0;int n = 100;mutex mtx;condition_variable cv;bool ready = true; // 开始标志,初始为 true,可以使线程 t2 先打印thread t1([&]() {while (i < n) {unique_lock<mutex> lock(mtx); // 条件变量需要的互斥锁cv.wait(lock, [&ready]() {return !ready; }); // 如果 ready 为 true 则阻塞cout << this_thread::get_id() << ":" << i << endl;++i;ready = true; // 更改条件cv.notify_one();// 唤醒另一个线程}});thread t2([&]() {while (i < n) {unique_lock<mutex> lock(mtx);cv.wait(lock, [&ready]() {return ready; }); // 如果 ready 为 false 则阻塞cout << this_thread::get_id() << ":" << i << endl;++i;ready = false; // 更改条件cv.notify_one();// 唤醒另一个线程}});t1.join();t2.join();return 0;
}
例2:生产者消费者模型
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>std::mutex mtx; // 互斥锁
std::condition_variable cv; // 条件变量
std::queue<int> q; // 共享队列
bool finished = false; // 结束标志void producer(int n) {for (int i = 0; i < n; ++i) {std::unique_lock<std::mutex> lock(mtx); // 加锁std::this_thread::sleep_for(std::chrono::milliseconds(100));q.push(i); // 生产数据std::cout << "produced " << i << "\n";lock.unlock(); // 解锁cv.notify_one(); // 通知消费者}{std::unique_lock<std::mutex> lock(mtx); // 加锁finished = true; // 设置结束标志}cv.notify_all(); // 通知所有消费者
}void consumer() {while (true) {std::unique_lock<std::mutex> lock(mtx); // 加锁cv.wait(lock, [] {return finished || !q.empty(); }); // 等待条件成立:结束或队列非空if (finished && q.empty()) {break; // 结束且队列空,退出循环}std::this_thread::sleep_for(std::chrono::milliseconds(100));int x = q.front(); // 取出数据q.pop();std::cout << std::this_thread::get_id() << ": consumed " << x << "\n";}
}int main() {std::thread t1(producer, 10); // 创建生产者线程,生产10个数据std::thread t2(consumer); // 创建消费者线程1std::thread t3(consumer); // 创建消费者线程2t1.join();t2.join();t3.join();return 0;
}
运行结果:

其他线程安全问题
shared_ptr
Q:shared_ptr 是线程安全的吗?
A:shared_ptr 的引用计数保证是线程安全的,但访问资源不是,如果你想在多个线程之间共享同一个 shared_ptr 指向的对象,你需要使用互斥锁或原子操作来保护它。
单例模式
饿汉模式:
- 特点:
- 不允许随便创建对象,构造函数私有
- 防拷贝
- main 函数之前就创建初始化对象
- 缺点:
- 对象初始化麻烦,影响程序启动速度
- 多个单例类,如果有依赖顺序关系,无法控制
懒汉模式:
- 特点:
- 不允许随便创建对象,构造函数私有
- 防拷贝
- 第一次使用对象时,创建对象
- 缺点:
- 多个线程使用,第一次调用存在竞争的线程安全问题。
所以饿汉模式不用考虑线程安全问题,而懒汉模式有线程安全问题
下面是一个懒汉模式的代码示例:
class Singleton {
public:static Singleton* GetInstance() {if (_spInst == nullptr) {_spInst = new Singleton;}return _spInst;}
private:Singleton() {}Singleton(const Singleton&) = delete;static Singleton* _spInst;
};Singleton* Singleton::_spInst = nullptr;
如果有多个线程同时调用 GetInstance,可能会创建多个对象实例,违反了单例模式的原则。
这个问题可以通过加锁解决
下面的代码是一个双重检查加锁的懒汉模式的实现,双重检查加锁是一种用于减少获取锁的开销的软件设计模式。程序先检查锁定条件,只有当检查表明需要锁定时才获取锁,然后检查是否已经实例化对象,防止创建多个对象。
class Singleton {
public:static Singleton* GetInstance() {if (_spInst == nullptr) { // 第一重检查std::unique_lock<std::mutex> lock(_mtx);if (_spInst == nullptr) { // 第二重检查_spInst = new Singleton;}}return _spInst;}
private:Singleton() {}Singleton(const Singleton&) = delete;static Singleton* _spInst;static std::mutex _mtx;
};Singleton* Singleton::_spInst = nullptr;
std::mutex Singleton::_mtx;
你会发现这两重检查缺一不可,如果缺少了第一重检查,那么每次调用 GetInstance 都会加锁解锁,增加了获取锁的开销。如果缺少了第二重检查,那么两个线程会竞争一把锁,当其中一个线程加锁之后,另一个线程会阻塞,当一个线程完成了对象的实例化,并释放锁,另一个线程就会获取锁,此时如果没有第二重检查,那么它就会再创建一个对象,违反了单例模式的原则。
第二种线程安全的懒汉模式的实现方式:
它利用了静态局部对象只会在第一次调用时初始化特性。
class Singleton {
public:static Singleton* GetInstance() {static Singleton _s;return &_s;}
private:Singleton() {}Singleton(const Singleton&) = delete;Singleton& operator=(Singleton const&) = delete;
};
但是这种实现方式有一个缺点,它在 C++11 之前不能保证是线程安全的,因为 C++11 之前局部静态对象的构造函数并不能保证是线程安全的。
相关文章:
C++ 线程库
文章目录thread 创建mutexmutexrecursive_mutextimed_mutexlock_guard原子操作atomic条件变量condition_variable其他线程安全问题shared_ptr单例模式C 线程库是 C11 标准中引入的一个特性,它使得 C 在语言级别上支持多线程编程,不需要依赖第三方库或操作…...
python字典和集合——笔记
一、介绍 1、泛映射类型 collections.abc模块中有Mapping和MutableMapping这两个抽象基类,它们的作用是为dict和其他类似的类型定义形式接口(在Python 2.6到Python 3.2的版本中,这些类还不属于collections.abc模块,而是隶属于coll…...
TEX:显示文本
文章目录字体选择字体fontspec宏包根据字体形状控制字体为不同的字体形状选择不同的特征为不同的字体大小状选择不同的特征中文字体选择xeCJK宏包字体选择与设置XELATEX字体名查找字体集与符号居中与缩进居中单边调整两边缩进诗歌缩进列表itemize样例enumerate样例description样…...
SS-ELM-AE与S2-BLS相关论文阅读记录
Broad learning system for semi-supervised learning 摘要:本文认为,原始BLS采用的稀疏自编码器来生成特征节点是一种无监督学习方法,这意味着忽略了标注数据的一些信息,并且难以保证同类样本之间的相似性和相邻性,同…...
ESP32设备驱动-MAX6675冷端补偿K热电偶数字转换器
MAX6675冷端补偿K热电偶数字转换器 1、MAX6675介绍 MAX6675执行冷端补偿并将来自K型热电偶的信号数字化。 数据以 12 位分辨率、SPI™ 兼容的只读格式输出。 该转换器可将温度解析为 0.25C,读数高达 +1024C,并且在 0C 至 +700C 的温度范围内具有 8 LSB 的热电偶精度。 MAX…...
Python基础知识汇总(字符串四)
目录 字母的大小写转换 lower()方法 upper()方法 删除字符串中的空格和特殊字符 strip()方法...
C语言学习笔记——指针(初阶)
前言 指针可以说是C语言基础语法中最难的理解的知识之一,很多新手(包括我)刚接触指针时都觉得很难。在我之前发布的笔记中都穿插运用了指针,但是我一直没有专门出一期指针的笔记,这是因为我确实还有些细节至今还不太清…...
阿赵的MaxScript学习笔记分享十二《获取和导出各种数据》
大家好,我是阿赵,周日的早上继续分享MaxScript学习笔记,这是第十二篇,获取和导出各种数据 1、导出数据的目的 使用3DsMax建立3D模型后,很多时候需要输出模型到别的引擎去使用,常用的格式有Obj、FBX、SLT等…...
react-draggable实现拖拽详解
react-draggable属性常用属性属性列表事件列表举例首先安装 react-draggable实现移动希望小编写的能够帮助到你😘属性 常用属性 属性默认值介绍axisxhandle拖动的方向,可选值 x ,y,bothhandle无指定拖动handle的classposition无handle的位置࿰…...
01.进程和线程的区别
进程和线程的区别进程和线程是计算机中的两个核心概念,它们都是用来实现并发执行的方式,但是它们在实现并发的方式和资源管理方面有一些重要的区别。进程是一个程序的运行实例。每个进程都有自己的内存空间、代码、数据和系统资源(如文件描述…...
逻辑优化-rewrite
简介 逻辑综合中的rewrite算法是一种常见的优化算法,其主要作用是通过对逻辑电路的布尔函数进行等效变换,从而达到优化电路面积、时序和功耗等目的。本文将对rewrite算法进行详细介绍,并附带Verilog代码示例。 一、算法原理 rewrite算法的…...
文件传输与聊天系统设计
技术:Java等摘要:本文介绍了一种基于TCP/IP协议使用Socket技术实现的聊天室系统,包括私聊功能和文件传输功能,对系统的主要模块进行了分析,并对系统实现过程中遇到的关键性技术进行了阐述,最后对系统进行了…...
蓝桥杯第十四届校内赛(第三期) C/C++ B组
一、填空题 (一)最小的十六进制 问题描述 请找到一个大于 2022 的最小数,这个数转换成十六进制之后,所有的数位(不含前导 0)都为字母(A 到 F)。 请将这个数的十进制形式作…...
有关平方或高次方的公式整理一元高次方程的求解
Part.I Introduction 这篇博文记录一下数学中常用的有关平方或高次方的一些公式。 Chap.I 一些结论 下面一部分汇总了一些重要的结论 完全平方公式:(ab)2a22abb2(ab)^2a^22abb^2(ab)2a22abb2平方差公式:a2−b2(ab)(a−b)a^2-b^2(ab)(a-b)a2−b2(ab)(…...
Java笔记3
ArrayListArrayList<String> list new Arraylist<>();<>是泛型表示存放的数据类型,注意不能是基本数据类型;增删改查增:add 返回值为true删:remove 1.直接删元素2.根据索引删元素改:set(…...
Leetcode.2202 K 次操作后最大化顶端元素
题目链接 Leetcode.2202 K 次操作后最大化顶端元素 Rating : 1717 题目描述 给你一个下标从 0开始的整数数组 nums,它表示一个 栈 ,其中 nums[0]是栈顶的元素。 每一次操作中,你可以执行以下操作 之一 : 如果栈非空…...
JAVA知识点全面总结3:String类的学习
三.String类学习 1.String,StringBuffer,StringBuilder的区别? 2.字符串拼接用加号的原理 ? 3.字符串常量池如何理解? 4.String的intern方法理解? 5.String的equals方法和compareTo方法的使用…...
Eureka注册中心和Nacos注册中心详解以及Nacos与Eureka有什么区别?
目录:前言Eureka注册中心Nacos注册中心Nacos与Eureka有什么区别?前言提供接口给其它微服务调用的微服务叫做服务提供者,而调用其它微服务提供的接口的微服务则是服务消费者。如果服务A调用了服务B,而服务B又调用了服务C࿰…...
Web3D发展趋势以及Web3D应用场景
1,Web3D发展趋势随着互联网的快速发展,Web3D技术也日渐成熟,未来发展趋势也值得关注。以下是Web3D未来发展趋势的七个方面:可视化和可交互性的增强:Web3D可以为三维数据提供可视化和可交互性的增强,将极大地…...
2023-3-4 刷题情况
按位与为零的三元组 题目描述 给你一个整数数组 nums ,返回其中 按位与三元组 的数目。 按位与三元组 是由下标 (i, j, k) 组成的三元组,并满足下述全部条件: 0 < i < nums.length 0 < j < nums.length 0 < k < nums.l…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
rm视觉学习1-自瞄部分
首先先感谢中南大学的开源,提供了很全面的思路,减少了很多基础性的开发研究 我看的阅读的是中南大学FYT战队开源视觉代码 链接:https://github.com/CSU-FYT-Vision/FYT2024_vision.git 1.框架: 代码框架结构:readme有…...
负载均衡器》》LVS、Nginx、HAproxy 区别
虚拟主机 先4,后7...
