C++11 简单手撕多线程编程
如何使用线程库
std::thread 创建线程
thread1.join(); 阻塞主线程
thread1.detach(); 线程分离
#include<iostream>
#include<thread>void helloworld(std::string msg) {for (int i = 0; i < 10000; i++){std::cout << i << std::endl;}//std::cout << msg << std::endl;return;
}int main() {//1. 创建线程std::thread thread1(helloworld, "hello Tread");//thread1.join();//thread1.detach();bool isJoin = thread1.joinable();if (isJoin) {thread1.join();};std::cout << "over" << std::endl;
}
线程中常见的数据传递错误
临时变量
#include <iostream>
#include <thread>
void foo(int& x) {x += 1;
}
int main() {int x = 1; // 将变量复制到一个持久的对象中std::thread t(foo, std::ref(x)); // 将变量的引用传递给线程,不可以传递临时变量t.join();return 0;
}
传递指针,需要将指针或引用指向堆上的变量,或使用std::shared_ptr等智能指针来管理对象的生命周期。
#include <iostream>
#include <thread>void foo(int* ptr) {std::cout << *ptr << std::endl;delete ptr; // 在使用完指针后,需要手动释放内存
}
int main() {int* ptr = new int(1); // 在堆上分配一个整数变量std::thread t(foo, ptr); // 将指针传递给线程t.join();return 0;
}
#include <iostream>
#include <thread>
#include <memory> // 引入shared_ptr的头文件void foo(std::shared_ptr<int> ptr) {std::cout << *ptr << std::endl;
}int main() {std::shared_ptr<int> ptr = std::make_shared<int>(1); // 使用make_shared分配内存std::thread t(foo, ptr); // 传递shared_ptr到线程t.join();return 0;
}
传递指针或引用指向已释放的内存的问题
#include <iostream>
#include <thread>void foo(int& x) {std::cout << x << std::endl;
}
int main() {int x = 1;std::thread t(foo, std::ref(x)); // 将变量的引用传递给线程,在线程函数执行期间,变量`x`的生命周期是有效的。t.join();return 0;
}
类对象被提前释放
#include <iostream>
#include <thread>
#include <memory>class MyClass {
public:void func() {std::cout << "Thread " << std::this_thread::get_id() << " started" << std::endl;std::cout << "Thread " << std::this_thread::get_id() << " finished" << std::endl;}
};int main() {std::shared_ptr<MyClass> obj = std::make_shared<MyClass>();std::thread t(&MyClass::func, obj);//避免obj被提前销毁,导致未定义的行为t.join();return 0;
}
入口函数为类的私有成员函数
#include <iostream>
#include <thread>class MyClass {
private:friend void myThreadFunc(MyClass* obj);void privateFunc(){std::cout << "Thread " << std::this_thread::get_id() << " privateFunc" << std::endl;}
};void myThreadFunc(MyClass* obj) {obj->privateFunc();
}int main() {MyClass obj;std::thread thread_1(myThreadFunc, &obj);thread_1.join();return 0;
}
将 myThreadFunc 定义为 MyClass 类的友元函数,并在函数中调用 privateFunc 函数。在创建线程时,需要将类对象的指针作为参数传递给线程。
多线程数据共享
互斥锁 (mutex)
#include <iostream>
#include <thread>
#include <mutex>int shared_data = 0;
std::mutex mtx;void func(int n) {for (int i = 0; i < 1000; ++i) {mtx.lock();shared_data++;std::cout << "Thread " << n << " increment shared_data to " << shared_data << std::endl;mtx.unlock();}
}
int main() {std::thread t1(func, 1);std::thread t2(func, 2);t1.join();t2.join();std::cout << "Final shared_data = " << shared_data << std::endl;return 0;
}
互斥量死锁
如果 T1 获取了 mtx1 的所有权,但是无法获取 mtx2 的所有权,而 T2 获取了 mtx2 的所有权,但是无法获取 mtx1 的所有权,两个线程互相等待对方释放互斥量,就会导致死锁。解决办法,需要资源顺序化,或者通过一次性尝试锁定所有指定的互斥锁,确保要么成功获取所有锁,要么释放已持有的锁并重新尝试,从而避免了死锁。
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx1, mtx2;void func1() {// 锁定 mtx2,然后再锁定 mtx1mtx2.lock();std::cout << "Thread 1 locked mutex 2" << std::endl;mtx1.lock();std::cout << "Thread 1 locked mutex 1" << std::endl;// 解锁 mtx1 和 mtx2mtx1.unlock();std::cout << "Thread 1 unlocked mutex 1" << std::endl;mtx2.unlock();std::cout << "Thread 1 unlocked mutex 2" << std::endl;
}void func2() {// 锁定 mtx2,然后再锁定 mtx1mtx2.lock();std::cout << "Thread 2 locked mutex 2" << std::endl;mtx1.lock();std::cout << "Thread 2 locked mutex 1" << std::endl;// 解锁 mtx1 和 mtx2mtx1.unlock();std::cout << "Thread 2 unlocked mutex 1" << std::endl;mtx2.unlock();std::cout << "Thread 2 unlocked mutex 2" << std::endl;
}int main() {// 启动两个线程,分别执行 func1 和 func2std::thread t1(func1);std::thread t2(func2);// 等待两个线程结束t1.join();t2.join();return 0;
}
lock_guard 与 std::unique_lock
使用 std::lock 和 std::lock_guard 或 std::unique_lock,它们能确保同时尝试锁定多个互斥量,并且不会死锁。
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx1, mtx2;void func1() {std::lock(mtx1, mtx2); // 同时锁定两个互斥量,避免死锁std::lock_guard<std::mutex> lk1(mtx1, std::adopt_lock); // adopt_lock表示互斥量已经被锁定std::lock_guard<std::mutex> lk2(mtx2, std::adopt_lock);std::cout << "Thread 1 locked mutexes 1 and 2" << std::endl;// 自动解锁
}void func2() {std::lock(mtx1, mtx2); // 同时锁定两个互斥量std::lock_guard<std::mutex> lk1(mtx1, std::adopt_lock);std::lock_guard<std::mutex> lk2(mtx2, std::adopt_lock);std::cout << "Thread 2 locked mutexes 1 and 2" << std::endl;// 自动解锁
}int main() {std::thread t1(func1);std::thread t2(func2);t1.join();t2.join();return 0;
}
std::unique_lock 提供了以下几个成员函数:
- lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁。
- try_lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则函数立即返回 false,否则返回 true。
- try_lock_for(const std::chrono::duration<Rep, Period>& rel_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间。
- try_lock_until(const std::chrono::time_point<Clock, Duration>& abs_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间点。
- unlock():对互斥量进行解锁操作。
call_once
单例设计模式是一种常见的设计模式,用于确保某个类只能创建一个实例。由于单例实例是全局唯一的,因此在多线程环境中使用单例模式时,需要考虑线程安全的问题。
全局只需要一个该类的对象,
下面是一个简单的单例模式的实现:
#include<iostream>
#include<thread>
#include<mutex>
#include<string>class Log {Log() {};
public:Log(const Log& log) = delete;Log& operator = (const Log &log) = delete;static Log& GetInstance() {//static Log log;//return log;static Log *log = nullptr;if (!log) log = new Log;return *log;}void PrintLog(std::string msg) {std::cout<<__TIME__<< msg << std::endl;};
};int main() {Log::GetInstance().PrintLog("error");
}
使用 std::call_once 可以确保单例实例只会被创建一次,从而避免了多个对象被创建的问题。此外,使用 std::unique_ptr 可以确保单例实例被正确地释放,避免了内存泄漏的问题。
#include<iostream>
#include<string>
#include <thread>
#include <mutex>
class Log
{
public:static Log& GetInstance(){//static Log log; //饿汉模式//return log;std::call_once(once, initfunc);return *log;}static void initfunc(){if (!log){log = new Log;}}void PrintLog(std::string msg){std::cout << __TIME__ << " " << msg << std::endl;}private:Log() {}~Log() {}Log(const Log&) = delete;Log& operator=(const Log&) = delete;//静态成员必须类外初始化static Log* log;static std::once_flag once;
};Log* Log::log = nullptr;std::once_flag Log::once; void print_error()
{Log::GetInstance().PrintLog("error");
}int main()
{std::thread t1(print_error);std::thread t2(print_error);t1.join();t2.join();return 0;
}
condition_variable
生产者与消费者模型
跨平台线程池
异步并发
原子操作
相关文章:
C++11 简单手撕多线程编程
如何使用线程库 std::thread 创建线程 thread1.join(); 阻塞主线程 thread1.detach(); 线程分离 #include<iostream> #include<thread>void helloworld(std::string msg) {for (int i 0; i < 10000; i){std::cout << i << std::endl;}//std::cou…...
刷c语言练习题7(牛客网)
1、函数fun的声明为int fun(int *p[4]),以下哪个变量可以作为fun的合法参数() A、int a[4][4]; B、int **a; C、int **a[4] D、int (*a)[4]; 答案:B 解析:如果是fun的合法参数,那么其类型应该与定义函数fun中的参数类型…...
Web Worker和WebSocket
Web Worker和WebSocket协议都是Web开发中用于处理多线程和实时通信的技术,但它们的应用场景和工作原理有所不同。 Web Worker Web Worker是HTML5引入的一项技术,它允许JavaScript代码在后台线程中运行,从而实现真正的多线程处理。Web Worke…...
【LeetCode】动态规划—712. 两个字符串的最小ASCII删除和(附完整Python/C++代码)
动态规划—712. 两个字符串的最小ASCII删除和 前言题目描述基本思路1. 问题定义2. 理解问题和递推关系3. 解决方法3.1 动态规划方法3.2 空间优化的动态规划 4. 进一步优化5. 小总结 代码实现PythonPython3代码实现Python 代码解释 CC代码实现C 代码解释 总结: 前言 在字符串处…...
wordpress Contact Form 7插件提交留言时发生错误可能的原因
WordPress Contact Form 7 插件提交留言时发生错误可能有以下几种原因,并提供相应的解决方案: 1. 表单字段验证失败 原因: 用户输入的数据未通过表单字段的验证规则。 解决方案: – 检查表单字段的验证规则是否设置正确。 –…...
uibot发送邮件:自动化邮件发送教程详解!
uibot发送邮件的操作指南?uibot发送邮件的两种方式? 在现代办公环境中,自动化流程的引入极大地提高了工作效率。uibot发送邮件功能成为了许多企业和个人实现邮件自动化发送的首选工具。AokSend将详细介绍如何使用uibot发送邮件。 uibot发送…...
【PostgreSQL】PG数据库表“膨胀”粗浅学习
文章目录 1 为什么需要关注表膨胀?2 如何确定是否发生了表膨胀?2.1 通过查询表的死亡元组占比情况来判断膨胀率2.1.1 指定数据库和表名2.1.2 查询数据库里面所有表的膨胀情况 3 膨胀的原理3.1 什么是膨胀?膨胀率?3.2 哪些数据库元…...
力扣(leetcode)每日一题 871 最低加油次数 | 贪心
871. 最低加油次数 题干 汽车从起点出发驶向目的地,该目的地位于出发位置东面 target 英里处。 沿途有加油站,用数组 stations 表示。其中 stations[i] [positioni, fueli] 表示第 i 个加油站位于出发位置东面 positioni 英里处,并且有 f…...
ppt压缩文件怎么压缩?压缩PPT文件的多种压缩方法
ppt压缩文件怎么压缩?当文件体积过大时,分享和传输就会变得困难。许多电子邮件服务对附件的大小有限制,而在网络环境不佳时,上传和下载大文件可能耗时较长。此外,在不同设备上播放时,较大的PPT文件还可能导…...
2024.10月11日--- SpringMVC拦截器
拦截器 1 回顾过滤器: Servlet规范中的三大接口:Servlet接口,Filter接口、Listener接口。 过滤器接口,是Servlet2.3版本以来,定义的一种小型的,可插拔的Web组件,可以用来拦截和处理Servlet容…...
uniapp 锁屏显示插件 Ba-LockShow(可让vue直接具备锁屏显示能力)
简介 Ba-LockShow 是一款可以直接使uniapp的vue界面在锁屏页展示的插件。 支持使vue直接具备锁屏显示能力支持设置锁屏显示和不显示支持唤醒屏幕 截图展示(仅参考) 支持定制、本地包、源码等,有建议和需要,请点击文章结尾“Unia…...
CSS计数器
CSS 中的计数器类似于变量,可以实现简单的计数功能,并将结果显示在页面上,在早期的网站上应用比较广泛。要实现计数器需要用到以下几个属性: counter-reset:创建或者重置计数器;counter-increment…...
嵌入式Linux:信号集
目录 1、信号集初始化 2、向信号集中添加或删除信号 3、测试信号是否在信号集中 在 Linux 系统中,处理多个信号时常用到一种数据结构:信号集(sigset_t)。信号集允许我们将多个信号组织在一起,以便在系统调用中传递和…...
Linux 外设驱动 应用 1 IO口输出
从这里开始外设驱动介绍,这里使用的IMX8的芯片作为驱动介绍 开发流程: 修改设备树,配置 GPIO1_IO07 为 GPIO 输出。使用 sysfs 接口或编写驱动程序控制 GPIO 引脚。编译并测试。 这里假设设备树,已经配置好了。不在论述这个问题…...
基于SpringBoot+Vue+MySQL的留守儿童爱心网站
系统展示 用户前台界面 管理员后台界面 系统背景 随着现代社会的发展,留守儿童问题日益受到关注。传统的纸质管理方式已经无法满足现代人们对留守儿童爱心信息的需求。为了提高留守儿童爱心信息的管理效率,增加用户信息的安全性,并方便及时反…...
调用第三方接口
目录 一、分析给出的接口文档 二、请求体格式之间的区别 三、示例代码 一、分析给出的接口文档 一般的接口文档包括以下几大部分: 1、请求URL:http://{ip}:{port}/api/ec/dev/message/sendCustomMessageSingle 2、请求方式:POST、GET等 3、…...
JAVA 多线程入门例子:CountDownLatch
首先确定线程数量。如果数据集合的大小小于50,就只使用一个线程;否则使用5个线程。计算每个线程平均处理的数据数量sizePerThread以及余数remainder。在划分数据子集合时,对于每个线程的处理范围进行计算。如果有余数,就将余数依次…...
k8s jenkins 动态创建slave
k8s jenkins 动态创建slave 简述使用jenkins动态slave的优势:配置jenkins动态slave配置 Pod Template配置容器模板挂载卷 测试 简述 持续构建与发布是我们日常工作中必不可少的一个步骤,目前大多公司都采用 Jenkins 集群来搭建符合需求的 CI/CD 流程&am…...
MVS海康工业相机达不到标称最大帧率
文章目录 一、相机参数设置1、取消相机帧率限制2、修改相机图像格式3、调整相机曝光时间4、检查相机数据包大小(网口相机特有参数)5、 恢复相机默认参数6、 相机 ADC 输出位深调整 二、系统环境设置1、 网口相机设置2、 USB 相机设置 一、相机参数设置 …...
数据结构:用双栈实现一个队列
要用两个栈实现一个队列,可以利用“栈”的后进先出 (LIFO) 特性来模拟“队列”的先进先出 (FIFO) 操作。具体做法是使用两个栈:一个作为入栈栈,另一个作为出栈栈。 算法步骤 入队操作(enqueue): 将元素压…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果,可同时显示主类&#x…...
智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...
