C++多线程初步

news/2025/4/3 4:37:49/文章来源:https://www.cnblogs.com/qinbaoyang/p/18782927

1.多线程初步

1.包含的库

#Include<thread>

2.涉及到的类 std::thread(这个类是属于标准模版库的,底层封装的系统调用)
3.代码实例

#include <iostream>
#include <thread>  
void hello(){    std::cout << "Hello World" << std::endl;
}
int main(){std::thread t(hello);
    // std::thread thread{ [] {std::cout << "Hello World!\n"; } };t.join();
    return 0;
}

线程函数含有参数的时候,将参数写在后面即可

void print(int x) { std::cout << x;
}
int main() {int value = 42;thread t(print,value);
    t.join();return 0;
}

特殊例子,当参数传参方式为引用时

#include <iostream>
#include <thread>
#include <functional> void increment(int& x) { x*=2;
}
int main() {int value = 42;
    //补全代码std::thread t(increment,std::ref(value));
    std::cout<<value; //期望是84return 0;
}

2.共享数据与线程同步

1.互斥量的介绍

当我们有多个线程任务对同一块共享数据进行读写时,为了协调这些线程对共享数据的访问,我们进入互斥量这一概念,在C++中,也就是std::mutex

当第一个线程执行到lock的时候,它拿到cpu的执行权,继续执行线程逻辑。当另外的线程执行到lock的时候,它会暂时陷入自旋,此时它会独占一个线程通道,自选期间如果lock的线程执行了unlock它就可以继续执行下去。但是如果lock的线程迟迟执行不到unlock,那么操作系统是不会允许这个线程通道一直被这个自旋的线程独占,这时候会从用户态转入内核态,让出线程通道来让别的线程执行。

还有一种不需要进入内核态的锁,是std::atomic ,原子变量的加速无需经过操作系统的处理,是通过编译器用原子指令实现的,所以原子量的效率优于线程锁。

2.关于粒度

锁本身不会造成严重的性能开销,但是加锁不当会导致程序转入内核态,这个过程有巨大的性能开销,所以我们在写C++代码的时候除了要注意共享数据的安全性,也要注意程序的性能,核心要点就是不要让一个线程陷入长时间的等待。

程序在哪些关键步骤需要加锁被称之为锁的粒度,如果程序上锁了无需上锁的语句,在逻辑和功能上看起来没有任何问题,但是却会导致性能上面的问题,所以上锁的时候我们首先应该关心锁的正确性,其次就是关心锁的粒度,既在一个多线程环境下,哪些步骤是必须需要加锁的,哪些步骤无需加锁。

3.lock_guard

  • lock_guard 是一个RAII(资源获取即初始化)风格的锁管理类,用于自动管理互斥锁(mutex)的加锁和解锁。
  • 它在构造时锁定互斥锁,在析构时自动释放锁,确保即使发生异常也不会忘记解锁。
    注意:lock_guard不支持手动lock()unlock()通常与{}结合来控制其生命期,进而控制临界区

4.thread_local

  • thread_local 是一个存储类说明符,用于声明线程局部变量
  • 每个线程都有其独立的变量副本,线程之间不会共享这些变量。
    注:访问thread_local的开销比普通变量大
    实例代码
#include<iostream>
#include<thread>
#include<mutex>thread_local int tls_var=0;
std::mutex m;
void thread_func(int id){tls_var=id;std::lock_guard<std::mutex>lg(m);//锁的自动释放和上锁std::cout<<"thread id="<<std::this_thread::get_id()<<",  tls_var="<<tls_var<<std::endl;
}
void case05(){std::thread t1(thread_func,1);std::thread t2(thread_func,2);t1.join();t2.join();
}

5.unique_lock

  • unique_lock 是一个更灵活的锁管理类,与lock_guard类似,但提供了更多功能。
  • 它支持手动加锁解锁、延迟加锁、条件变量等高级功能。
    代码实例

6.关于死锁

两个或多个线程在执行过程中,因为争夺资源而造成的一种互相等待的现象,导致这些线程都无法继续执行下去。

例:比如我们有两个线程代表两台服务器,一个服务器用于数据查询存储,一台用于数据计算。然后我们日常生活中有两种任务,一种任务是先查询数据,然后使用查询到的数据进行计算。另一种是先计算数据,然后将计算结果保存。这时候如果我们仅仅对每台服务器单独加锁,就会出现一种场景,当两种任务同时到来的时候,任务A执行完成想要获取计算服务器,但是计算服务器被任务B占用;任务B想要获取查询存储服务器但是被任务A占用。

解决死锁总的来说有两种解法:一种是我们不对可能出现冲突的局部加锁,而是对整个任务加锁。就像上面的例子中,当任务A来的时候,同时对数据服务器和计算服务器加锁,计算完成之后释放。这样就不会出现死锁。
另一种解法是当一个任务持有锁超时之后就释放任务,短暂等待后重新执行任务。
问题:这两种解法分别适用于什么场景?

7.关于条件变量

日常生活中有一类常见的问题,那就是生产者消费者问题,这类型问题几乎无处不在。比如我们观看在线视频。这里有一个逻辑就是视频的数据来源于网络,数据的使用者是本地播放器。因此这里就有一个模型,即只有下载够一定数量的数据的时候播放器才开始播放,当下载数据超过一个限度的时候就停止缓存,当播放(使用数据)到一定程度的时候又继续下载。
这个模型里下载器扮演了生产者的角色,播放器扮演了消费者的角色。

实例代码:
情景
我们假设一个场景,有一个下载线程和一个播放线程,下载线程检测到当前没有任何数据就开始下载,直到下载数据数量达到5(我们假设一次下载任务执行可以下载一个数据),下载数量达到5之后下载线程停止工作,播放线程开始工作,直到消耗完下载线程的所有数据。请用C++代码表示这个过程。
我们假设上面的例子不是一次性的,而是有一个场景,在这个场景下我们这两个线程长期存在,线程A会一致下载文件到本地磁盘中,当线程A下载文件大小占满磁盘空间时候,我们就暂停下载并且开启B线程,B线程会解析处理这些文件,然后将其删除,当B线程删除文件到磁盘空间为0的时候,我们就暂停B线程并重新启动A线程。

std::mutex mut;
std::condition_variable cond_var;int disk_capacity = 3;std::mutex print_mutex;
void safe_print(const std::string& msg)
{std::lock_guard<std::mutex> guard(print_mutex);std::cout << msg << std::endl;
}void download_file()
{std::unique_lock<std::mutex> lock(mut);while (true){cond_var.wait(lock,[]{return disk_capacity ;});while (disk_capacity){std::stringstream ss;ss << "files download ready, disk_capacity is " << disk_capacity;std::string formatted_string = ss.str();safe_print(formatted_string);std::this_thread::sleep_for(std::chrono::seconds(1));disk_capacity--;}cond_var.notify_all();}
}void get_files_do_something()
{std::unique_lock<std::mutex> lock(mut);while (true){cond_var.wait(lock, [] {return disk_capacity == 0;});//...解析文件,并且将磁盘清空disk_capacity = 3;std::stringstream ss;ss << "files delete, disk_capacity is " << disk_capacity << "-----" << std::this_thread::get_id();std::string formatted_string = ss.str();safe_print(formatted_string);cond_var.notify_all();}
}int main()
{std::thread t1(get_files_do_something);std::thread t2(download_file);std::thread t3(get_files_do_something);t1.join();t2.join();t3.join();return 0;
}

对于条件变量的补充:
1.几个重要方法

  • std::condition_variable::wait(...)如果谓词返回为true,则线程继续运行,返回为false,则线程阻塞,并且释放锁
  • std::condition_variable::notify_all() 唤醒其它所有线程,尝试拿锁,如果拿到锁,则做谓词条件判断,注意,此操作并不会使线程释放锁
    实例代码补充:
    第二版
#include<iostream>
#include<thread>
#include<mutex>
#include<queue>
#include<condition_variable>
/*
producer and consumer problem
*/
const int N=5;
std::queue<int>buffer;
std::mutex mtx;
std::condition_variable con_var;
void producer(){std::unique_lock<std::mutex>lock(mtx);while(1){con_var.wait(lock,[](){return buffer.size()==0;});for(int i=0;i<N;i++){buffer.push(i+1);std::cout<<"producer produce data:"<<i+1<<std::endl;}std::cout<<std::endl;std::this_thread::sleep_for(std::chrono::seconds(2)); con_var.notify_all();}
}
void consumer(int id){while(1){{std::unique_lock<std::mutex>lock(mtx);con_var.wait(lock,[](){return !buffer.empty(); });int data=buffer.front();buffer.pop();std::cout<<"consumer:"<<id<<" consume data:"<<data<<std::endl;if(buffer.empty()){std::cout<<std::endl;con_var.notify_all();} }std::this_thread::sleep_for(std::chrono::seconds(2));//超级底层,要是不做细微延时的话,会出现独占锁的情况}
}
void case06(){std::thread t1(producer);std::thread t2(consumer,1);std::thread t3(consumer,2);std::thread t4(consumer,3);std::thread t5(consumer,5);t1.join();t2.join();t3.join();return;
}

实例代码补充结果如下:

可以看到,当数据队列(共享资源)为空时,生产者线程一次性写5个数据,唤醒所有消费者线程;当数据队列不为空时,5个消费者线程并发的消费数据,当数据被消耗殆尽时,唤醒所有线程(其实旨在让生产者拿到锁,进入活动)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/909772.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

全定制电路Flow手册

简单做个summary手册,方便后面查阅以及组里统一规范。 全定制电路Flow手册 编写人:袁易扬 联系方式:2861704773@qq.com文档版本 编写日期 说明v1.0 2024.3.27 初次发布1. 工具链 原理图: Cadence Virtuoso IC617/618(用于22nm及以上的平面CMOS工艺) Cadence Virtuoso ICA…

Ajax、vue-cli、element

Ajax(Asynchronous JavaScript And XML)异步的JavaScript和XML 作用:数据交换:通过Ajax可以给服务器发送请求,并获取服务器响应的数据。 异步交互:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术Axios:对原生Ajax进行封装,简化书写,快速开…

Hutool工具TreeUtil构建树形结构

1.导入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-core</a…

20241101 2024-2025-2 《Python程序设计》实验二报告

20241101 2024-2025-2 《Python程序设计》实验一报告 课程:《Python程序设计》 班级:2411 姓名:苏萱 学号:20241101 实验教师:王志强 实验日期:2025.3.26 必修/选修: 公选课 (一)实验内容 1.设计并完成一个完整的应用程序,完成加减乘除模等运算,功能多多益善。 2.考核…

8种核心架构图作用解析

一、架构图是什么?架构图就像人体的骨架决定身体结构一样,架构图是组织/系统的"骨架说明书"。它把复杂事物最关键的顶层结构画成视觉地图,帮助我们5分钟看懂一个体系的构造。 常见的有8种类型,都是企业管理的"导航地图"。 二、8大核心架构图详解业务架…

2024-2025-2 《Python程序设计》实验二报告

学号 2024-2025-2 《Python程序设计》实验二报告 课程:《Python程序设计》 班级: 2413 姓名: 廖江泽 学号:20241311 实验教师:王志强 实验日期:2025年3月26日 必修/选修: 公选课 1.实验内容设计并完成一个完整的计算器,完成加减乘除模等运算,功能多多益善;考核基本语…

《C Primer Plus 中文版 (第6版)》2020版最新修订版PDF下载

《C Primer Plus(第6版)中文版》在之前版本的基础之上进行了全新升级,它涵盖了C语言最新的进展以及C11标准的详细内容。本书还提供了大量深度与广度齐备的教学技术和工具,来提高你的学习。内容简介豆瓣评分9.1 重量级C大百科全书 中文版累计销量近百万册! C图书领域的独孤…

蓝桥杯2

今天写了写选择结构的题

ubuntu配置cuda和cudnn

nvidia-smi 查看当前驱动 https://developer.nvidia.com/cuda-toolkit-archive 选择匹配的驱动下载.run文件 sudo sh cuda****如果已经安装过显卡驱动,就不要再重复安装驱动了,按enter取消driverDriver: Not Selected Toolkit: Installed in /usr/local/cuda-12.4/ Please…

卢曼卡片盒笔记法介绍 Introduction to the Zettelkasten Method

你可能在工作中遇到了迫切需要解决的问题,可能被硕士论文压得喘不过来气,你想要提升你的博客水平,想写一本书,或者想在科研上更进一步。但是有一点需要明确,卡片盒笔记法不仅是一个帮助你完成工作或项目的工具,更是管理你生活中所遇到的知识的方法。原文链接:https://ze…

javawebDay5-Maven框架

单元测试右侧参数代表测试当前类的各项比例 Class:调用测试类中类 Method:调用方法比例 Line:调用代码量比例问题:目前猜测是用户名为中文导致

量化的前期准备

我看了不少国内量化的软件, 很多都在说QMT 迅投公司的产品, 专卖给券商, 然后券商给优质用户开 号就可以了。 各个券商的要求各不一样。 我这个因为我很多板块要求50w 存20工作日, 放了进去,居然符合券商要求了,答应给我开通了。 其实现在 很多开源的 比如easy trader 的…