当前位置: 首页 > news >正文

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的合法参数&#xff08;&#xff09; A、int a[4][4]; B、int **a; C、int **a[4] D、int (*a)[4]; 答案&#xff1a;B 解析&#xff1a;如果是fun的合法参数&#xff0c;那么其类型应该与定义函数fun中的参数类型…...

Web Worker和WebSocket

Web Worker和WebSocket协议都是Web开发中用于处理多线程和实时通信的技术&#xff0c;但它们的应用场景和工作原理有所不同。 Web Worker Web Worker是HTML5引入的一项技术&#xff0c;它允许JavaScript代码在后台线程中运行&#xff0c;从而实现真正的多线程处理。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 插件提交留言时发生错误可能有以下几种原因&#xff0c;并提供相应的解决方案&#xff1a; 1. 表单字段验证失败 原因&#xff1a; 用户输入的数据未通过表单字段的验证规则。 解决方案&#xff1a; – 检查表单字段的验证规则是否设置正确。 –…...

uibot发送邮件:自动化邮件发送教程详解!

uibot发送邮件的操作指南&#xff1f;uibot发送邮件的两种方式&#xff1f; 在现代办公环境中&#xff0c;自动化流程的引入极大地提高了工作效率。uibot发送邮件功能成为了许多企业和个人实现邮件自动化发送的首选工具。AokSend将详细介绍如何使用uibot发送邮件。 uibot发送…...

【PostgreSQL】PG数据库表“膨胀”粗浅学习

文章目录 1 为什么需要关注表膨胀&#xff1f;2 如何确定是否发生了表膨胀&#xff1f;2.1 通过查询表的死亡元组占比情况来判断膨胀率2.1.1 指定数据库和表名2.1.2 查询数据库里面所有表的膨胀情况 3 膨胀的原理3.1 什么是膨胀&#xff1f;膨胀率&#xff1f;3.2 哪些数据库元…...

力扣(leetcode)每日一题 871 最低加油次数 | 贪心

871. 最低加油次数 题干 汽车从起点出发驶向目的地&#xff0c;该目的地位于出发位置东面 target 英里处。 沿途有加油站&#xff0c;用数组 stations 表示。其中 stations[i] [positioni, fueli] 表示第 i 个加油站位于出发位置东面 positioni 英里处&#xff0c;并且有 f…...

ppt压缩文件怎么压缩?压缩PPT文件的多种压缩方法

ppt压缩文件怎么压缩&#xff1f;当文件体积过大时&#xff0c;分享和传输就会变得困难。许多电子邮件服务对附件的大小有限制&#xff0c;而在网络环境不佳时&#xff0c;上传和下载大文件可能耗时较长。此外&#xff0c;在不同设备上播放时&#xff0c;较大的PPT文件还可能导…...

2024.10月11日--- SpringMVC拦截器

拦截器 1 回顾过滤器&#xff1a; Servlet规范中的三大接口&#xff1a;Servlet接口&#xff0c;Filter接口、Listener接口。 过滤器接口&#xff0c;是Servlet2.3版本以来&#xff0c;定义的一种小型的&#xff0c;可插拔的Web组件&#xff0c;可以用来拦截和处理Servlet容…...

uniapp 锁屏显示插件 Ba-LockShow(可让vue直接具备锁屏显示能力)

简介 Ba-LockShow 是一款可以直接使uniapp的vue界面在锁屏页展示的插件。 支持使vue直接具备锁屏显示能力支持设置锁屏显示和不显示支持唤醒屏幕 截图展示&#xff08;仅参考&#xff09; 支持定制、本地包、源码等&#xff0c;有建议和需要&#xff0c;请点击文章结尾“Unia…...

CSS计数器

CSS 中的计数器类似于变量&#xff0c;可以实现简单的计数功能&#xff0c;并将结果显示在页面上&#xff0c;在早期的网站上应用比较广泛。要实现计数器需要用到以下几个属性&#xff1a; counter-reset&#xff1a;创建或者重置计数器&#xff1b;counter-increment&#xf…...

嵌入式Linux:信号集

目录 1、信号集初始化 2、向信号集中添加或删除信号 3、测试信号是否在信号集中 在 Linux 系统中&#xff0c;处理多个信号时常用到一种数据结构&#xff1a;信号集&#xff08;sigset_t&#xff09;。信号集允许我们将多个信号组织在一起&#xff0c;以便在系统调用中传递和…...

Linux 外设驱动 应用 1 IO口输出

从这里开始外设驱动介绍&#xff0c;这里使用的IMX8的芯片作为驱动介绍 开发流程&#xff1a; 修改设备树&#xff0c;配置 GPIO1_IO07 为 GPIO 输出。使用 sysfs 接口或编写驱动程序控制 GPIO 引脚。编译并测试。 这里假设设备树&#xff0c;已经配置好了。不在论述这个问题…...

基于SpringBoot+Vue+MySQL的留守儿童爱心网站

系统展示 用户前台界面 管理员后台界面 系统背景 随着现代社会的发展&#xff0c;留守儿童问题日益受到关注。传统的纸质管理方式已经无法满足现代人们对留守儿童爱心信息的需求。为了提高留守儿童爱心信息的管理效率&#xff0c;增加用户信息的安全性&#xff0c;并方便及时反…...

调用第三方接口

目录 一、分析给出的接口文档 二、请求体格式之间的区别 三、示例代码 一、分析给出的接口文档 一般的接口文档包括以下几大部分&#xff1a; 1、请求URL&#xff1a;http://{ip}:{port}/api/ec/dev/message/sendCustomMessageSingle 2、请求方式&#xff1a;POST、GET等 3、…...

JAVA 多线程入门例子:CountDownLatch

首先确定线程数量。如果数据集合的大小小于50&#xff0c;就只使用一个线程&#xff1b;否则使用5个线程。计算每个线程平均处理的数据数量sizePerThread以及余数remainder。在划分数据子集合时&#xff0c;对于每个线程的处理范围进行计算。如果有余数&#xff0c;就将余数依次…...

k8s jenkins 动态创建slave

k8s jenkins 动态创建slave 简述使用jenkins动态slave的优势&#xff1a;配置jenkins动态slave配置 Pod Template配置容器模板挂载卷 测试 简述 持续构建与发布是我们日常工作中必不可少的一个步骤&#xff0c;目前大多公司都采用 Jenkins 集群来搭建符合需求的 CI/CD 流程&am…...

MVS海康工业相机达不到标称最大帧率

文章目录 一、相机参数设置1、取消相机帧率限制2、修改相机图像格式3、调整相机曝光时间4、检查相机数据包大小&#xff08;网口相机特有参数&#xff09;5、 恢复相机默认参数6、 相机 ADC 输出位深调整 二、系统环境设置1、 网口相机设置2、 USB 相机设置 一、相机参数设置 …...

数据结构:用双栈实现一个队列

要用两个栈实现一个队列&#xff0c;可以利用“栈”的后进先出 (LIFO) 特性来模拟“队列”的先进先出 (FIFO) 操作。具体做法是使用两个栈&#xff1a;一个作为入栈栈&#xff0c;另一个作为出栈栈。 算法步骤 入队操作&#xff08;enqueue&#xff09;&#xff1a; 将元素压…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

工业安全零事故的智能守护者:一体化AI智能安防平台

前言&#xff1a; 通过AI视觉技术&#xff0c;为船厂提供全面的安全监控解决方案&#xff0c;涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面&#xff0c;能够实现对应负责人反馈机制&#xff0c;并最终实现数据的统计报表。提升船厂…...

三维GIS开发cesium智慧地铁教程(5)Cesium相机控制

一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点&#xff1a; 路径验证&#xff1a;确保相对路径.…...

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下&#xff0c;无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作&#xff0c;还是游戏直播的画面实时传输&#xff0c;低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架&#xff0c;凭借其灵活的编解码、数据…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

STM32HAL库USART源代码解析及应用

STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

第7篇:中间件全链路监控与 SQL 性能分析实践

7.1 章节导读 在构建数据库中间件的过程中&#xff0c;可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中&#xff0c;必须做到&#xff1a; &#x1f50d; 追踪每一条 SQL 的生命周期&#xff08;从入口到数据库执行&#xff09;&#…...

高考志愿填报管理系统---开发介绍

高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发&#xff0c;采用现代化的Web技术&#xff0c;为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## &#x1f4cb; 系统概述 ### &#x1f3af; 系统定…...