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): 将元素压…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...
html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
c++第七天 继承与派生2
这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...
华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
数据结构:递归的种类(Types of Recursion)
目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...
