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): 将元素压…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...

【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...