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…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器
拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件: 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...
[论文阅读]TrustRAG: Enhancing Robustness and Trustworthiness in RAG
TrustRAG: Enhancing Robustness and Trustworthiness in RAG [2501.00879] TrustRAG: Enhancing Robustness and Trustworthiness in Retrieval-Augmented Generation 代码:HuichiZhou/TrustRAG: Code for "TrustRAG: Enhancing Robustness and Trustworthin…...
LTR-381RGB-01RGB+环境光检测应用场景及客户类型主要有哪些?
RGB环境光检测 功能,在应用场景及客户类型: 1. 可应用的儿童玩具类型 (1) 智能互动玩具 功能:通过检测环境光或物体颜色触发互动(如颜色识别积木、光感音乐盒)。 客户参考: LEGO(乐高&#x…...
本地部署drawDB结合内网穿透技术实现数据库远程管控方案
文章目录 前言1. Windows本地部署DrawDB2. 安装Cpolar内网穿透3. 实现公网访问DrawDB4. 固定DrawDB公网地址 前言 在数字化浪潮席卷全球的背景下,数据治理能力正日益成为构建现代企业核心竞争力的关键因素。无论是全球500强企业的数据中枢系统,还是初创…...
