C++【多线程】

news2024/9/29 21:28:02

文章目录

  • 一、什么是线程
  • 二、创建线程

一、什么是线程

线程在进程内部执行,是OS调度的基本单位。

linux进程

在堆区上存在下面一种数据结构

struct vm_area_struct{
//用来记录这块空间的起始和终止。
	unsigned long vm_start;
	unsigned long vm_end;
	//其实这是一个双向链表中的结点,用具记录前后的空间节点
	struct vm_ares_struct *vm_next ,*vm_prev;
}
如果我们的堆区申请了比较多的空间,然后我们的vm_area_struct就是用来记录
我们每一小块的地址空间的起始和结束。
然后这些小的内存块就通过双向链表的形式串联起来。

所以说,OS是可以做到让进程进行细粒度的划分的

用户级页表+MMU(是集成在CPU当中的)

我们如何从虚拟地址映射到物理地址?

1.exe就是一个文件
2.我们的可执行程序本来就是按照地址空间方式进行编译的
3.可执行程序,其实按照区域也已经被划分成了以4kb为单位的空间。

我们如何管理这里的每一个4kb的空间呢?
我们需要先描述,再组织,也就是用struct page结构体来进行描述

struct page
{
	int flag;
}

内核想要管理这么多物理内存,我们就需要创建一个数组struct page mem[100w+]
然后操作系统想要管理对应的物理内存的时候,就可以通过这一个数组进行管理。
所以操作系统对于物理内存的管理,就变成了对于对应的数据结构的管理。

磁盘中的可执行文件是按照4kb划分的,我们的物理内存也是按照4kb划分的,其中我们将磁盘当中以4kb为单位的,我们的代码的数据的内容,称之为页帧
我们物理内存这里的4kb大小称之为页框
IO的基本单位是4kb,IO就是将页帧装进页框里

在这里插入图片描述

缺页中断:如果我们的操作系统在寻值得时候,发现对应的数据不在我们的内存中,我们就需要去磁盘中读取对应的数据到我们的内存中,然后通过页表映射,获取到我们的数据。

我们的虚拟地址有232个(4GB,页表是保存在物理内存当中的),也就是说如果想要保存我们的一整张页表的话,需要的大小为页表的条目的大小×4GB,这样空间占用就会非常大。

但是我们可以按照下图建立一级页表和二级页表,来简化我们的索引。

在这里插入图片描述

如何理解线程

通过我们创建了多个task_strcu纸箱同一个mm_struct,通过一定的技术手段,
将当前进程的“资源”,以一定的方式划分给不同的task_struct
也就是说我们再创建task_struct的时候,不再去开辟新的资源了。
我们就将这里的每一个task_struct就称为线程。

在这里插入图片描述
什么是线程在进程内部执行?

线程在进程的地址空间内进行运行。

为什么线程是0S调度的基本单位?

因为cpu并不关心执行流是线程还是进程,只关心pcb。

这只是Linux下的维护方案,没有为线程设计专门的数据结构。
但只要比进程更轻量,粒度更轻,就是线程。
windows有为线程设计专门的数据结构。

什么是进程(资源角度)

进程就是我们对应的内核数据结构,再加上该进程所对应的代码和数据。
一个进程可能会有多个PCB。
在内核的时间,进程是承担系统分配资源的基本实体。

所以我们创建线程的时候,只有第一个需要申请资源,也就是我们上面图中红框的那一个task_struct,也就是一个进程,后面所创建的线程不是想操作系统索要资源,而是向我们的进程共享了资源。

如何理解我们曾经我们所写的所有的代码?

内部只有一个执行流的进程。
我们现在就可以创建内部具有多个执行流的进程。
我们的task_struct仅仅是我们的进程内部的一个执行流。

在CPU的视角,CPU其实不怎么关心当前是进程还是线程这样的概念,只人stask_struct。
我们的CPU的调度其实调度的是stack_struct

在Linux下,PCB<=其他操作系统的PCB的
Linux下的进程:统一称之为轻量级进程。
当CPU拿到一个PCB的时候,可以是单执行流的进程的PCB,也可可能是多执行流的其中一个线程的PCB,所以比那些别的操作系统单独给线程和进程设计的数据结构更加轻量

所以Linux没有真正意义上的线程结构,Linux上是用进程PCB模拟线程的。
所以Linux并不能直接给我们提供线程相关的接口,只能提供轻量级进程的接口(在用户层实现了一套用户层多线程方案,以库的方式提供给其他用户进行使用,pthread线程库–原生线程库)。

线程如何看待进程内部的资源呢?

原则向线程能够看到进程的所有资源,在进程的上下文中进行操作。

进程 vs 线程

调度层面:上下文(调度一个线程的成本比调度进程的成本更低)

二、创建线程

在这里插入图片描述
在这里插入图片描述

我们还需要在我们的makefile中添加-lpthead选项

mythread:mythread.cc
		g++ -o mythread mythread.cc -std=c++11  -lpthread
.PHONY:clean
clean:
		rm -f signal
#include<iostream>
#include<pthread.h>
#include<string>
#include<cstdio>
#include<unistd.h>
using namespace std;
void *threadRun(void *args)
{
    const string name=(char*)args;
    while(true)
    {
        //如果线程属于进程的话,我们这里获得的pid应该和我们的线程是同一个pid
        cout<<name<<", pid: "<<getpid()<<endl;
        cout<<endl;
        sleep(1);
    }
}
int main()
{
    //无符号长整数类型
    pthread_t tid[5];
    char name[64];
    //循环创建5个线程
    for(int i=0;i<5;i++)
    {
        //格式化我们线程的名字
        snprintf(name,sizeof name,"%s-%d","thread",i);
        pthread_create(tid+i,nullptr,threadRun,(void *)name);
        sleep(1);//缓解传参的bug
    }
    
	//我们的主线程在执行完上面的代码之后就会执行下面的代码。
    while(true)
    {
        cout<<"main thread, pid: "<<getpid()<<endl;
        sleep(3);
    }
}

我们这里看我们的程序已经链接上了我们的pthread_create库

在这里插入图片描述
在这里插入图片描述

但是我们再这里只能查看到到一个进程,我们如何查看到这个进程里面的线程呢?

在这里插入图片描述

编写监控脚本

ps -aL |head |head -1 && ps -aL| grep mythread

在这里插入图片描述

我们Linux内部所看的一定是LWP,不是看的PID。
如果只是单线程的话,这个进程的PID和LWP是相同的。

我们这里只要将我们的进程终止了,我们所有的线程都会终止。
因为我们现成的所以资源都是来自于我们的进程的,没有了代码和数据,当然会退出。

线程的共享资源

文件描述符表
每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
当前工作目录
用户id和组id
堆区可以被共享
共享区也是被所有线程共享的
栈区也是可以共享的,但我们一般不这么做。

线程的私有资源

线程ID
一组寄存器(线程的上下文)
栈
errno
信号屏蔽字
调度优先级

进程和线程切换,我们为什么说线程的切换成本更低?

如果我们调度的一个进程内的若干个线程,我们的地址空间不需要切换,页表也不需要切换
如果是进程切换的话,地址空间,页表等等都需要切换。

并且我们的CPU内是有硬件级别的缓存的(cache)(L1-L3)
我们只要将相关的数据load到我们CPU内部的缓存,对内存的代码和数据,根据局部性原理
(一条指令如果被使用了,它附近的代码也有很大的可能被使用),预读取到我们的CPU的缓存中,
这样我们的CPU就不需要访问内存,直接到缓存中访问就可以了。

但是如果进程切换,那么我们的cache立即失效,新进程过来的时侯,只能重新缓存。
所以我们的线程切换比我们的进程切换更加轻量化。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

判断环形链表是否有环??返回环形链表的入口点!!

上次笔者写了一篇大概有7个题的链表相关的题目解析&#xff0c;感觉还不错&#xff0c;感兴趣的各位老铁&#xff0c;可以点一下链接进行欣赏&#xff1a;做几个与链表相关的题吧&#xff01;https://blog.csdn.net/weixin_64308540/article/details/128550685?spm1001.2014.3…

CPT205-Computer Graphics(2)

文章目录7. Hierarchical Modelling7.1 Local and world co-ordinate frames of reference7.1.1 Relative motion7.2 Linear modelling7.3 Hierarchical modelling7.3.1 Hierarchical transformations8. Lighting and Materials8.1 Lighting sources8.1.1 Point light8.1.2 Dir…

如何将revit的内建模型导出使用?项目族管理功能介绍

Revit中内建模型建模是我们常用的功能&#xff0c;每次建模完成后都可以在另一个项目中使用&#xff0c;但是前提是需要导出后再载入才能在另一个项目中使用。今天就教教大家如何将Revit的内建模型单独导出&#xff0c;方便大家下次使用在其它项目。 如何将revit的内建模型单独…

计算机组成原理_总线的性能指标

计算机组成原理总目录总线的性能指标 一、总线周期和时钟周期 总线周期 总线周期通常指的是CPU完成一次访问内存或I/O端口操作所需要的时间 其中包括申请阶段、寻址阶段、传输阶段和结束阶段时钟周期 时钟周期是处理操作最基本的单位&#xff0c;在一个时钟周期内&#xff0c;…

【历史上的今天】1 月 9 日:iPhone 问世;iTunes 发布;激光打印机的发明者出生

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 1 月 9 日&#xff0c;在 1978 年的这段时间&#xff0c;我国恢复了研究生制度&#xff0c;这一年&#xff0c;共录取了 10500 多名研究生。研究生教育的中断和…

哈希的应用 —— 布隆过滤器

目录 一、布隆过滤器的提出 二、布隆过滤器的介绍 1. 基本概念 2. 布隆过滤器的特点 3. 哈希函数和布隆过滤器的长度对误判的影响 三、布隆过滤器的实现 1. 布隆过滤器的插入 &#xff08;Set接口&#xff09; 2. 布隆过滤器的查找&#xff08;Test接口&#xff09; …

javascript原型之保姆级讲解

目录前言一&#xff0c;面向对象编程1.1 面向过程与面向对象1.2 JS创建类和对象1.3 类的继承1.4 Super关键词1.5 几个注意点二&#xff0c;构造函数原型2.1 创建对象的三种方法2.2 静态成员和实例成员2.3 构造函数的弊端2.4 函数的共享-原型prototype2.5 对象原型___proto__2.6…

用Python制作你的专属音乐播放器(此刻浪漫只属于你哦*´▽`*)

文章目录前言一、项目介绍二、环境配置三、代码实战前言 昨天是博主的一位朋友生日&#xff0c;除了送上大大的红包&#xff0c;知道他喜欢听音乐&#xff0c;特意用代码给他写了一个 专属音乐播放器&#xff0c;今天把这个代码也开源送给所有粉丝哦。 一、项目介绍 我们常用…

Vue--》实现动画与过渡效果

目录 动画效果 过渡效果 第三方动画库 Vue封装的过渡与动画作用是在插入、更新或移除DOM元素时&#xff0c;在合适的时候给元素添加类名。 动画效果 操作css的trasition或animation&#xff0c;vue会给目标元素添加/移除特定的class&#xff0c;过渡的相关类名是&#xff1…

Spring项目中用了这种解耦模式,经理对我刮目相看

前言 不知道大家在项目中有没有遇到过这样的场景&#xff0c;根据传入的类型&#xff0c;调用接口不同的实现类或者说服务&#xff0c;比如根据文件的类型使用 CSV解析器或者JSON解析器&#xff0c;在调用的客户端一般都是用if else去做判断&#xff0c;比如类型等于JSON&…

【Spring6源码・IOC】Bean的实例化

上一节我们讲到《【Spring6源码・IOC】BeanDefinition的加载》&#xff0c;这一节我们来讲解一下Bean的实例化。 根据xml或注解加载完Bean的信息后&#xff0c;最终会通过反射来创建bean的对象。 invokeBeanFactoryPostProcessors()主要是加载BeanDefinition。 finishBeanFa…

MAC(m1)-VMWare Fusion安装CentOS7.9(续)

下载了CentOS8&#xff0c;优先安装CentOS8 默认的配置有点小, 可以根据自己需求进行自定义设置&#xff1a; 点击存储&#xff0c;会出现&#xff1a; 咱们是中国人&#xff0c;优先选择中文&#xff1a; 点击网络和主机名&#xff0c;配置网络&#xff1a; 打开网卡开关&…

找出给定数组中和是给定目标整数的两个整数,输出找到的两个整数下标

找出给定数组中和是给定目标整数的两个整数&#xff0c;输出找到的两个整数下标。 (本文获得CSDN质量评分【x】)【学习的细节是欢悦的历程】Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅…

Android---简易Snackbar

目录 Snackbar 简介 Snackbar 特性 完整Demo Snackbar 简介 Snackbar 是 Android5.0 新特性---Material Design 中的一个控件&#xff0c;用来代替 Toast。Snackbar 就是一个类似 Toast 的快速弹出消息提示的控件&#xff0c;手机上显示在底部&#xff0c;大屏幕设备显示在左…

Golang实现分布式锁

1. go实现分布式锁 通过 golang 实现一个简单的分布式锁&#xff0c;包括锁续约、重试机制、singleflght机制的使用 1.1 redis_lock.go package redis_lockimport ("context"_ "embed""errors""github.com/go-redis/redis/v9""…

Java Netty框架自建DNS代理服务器教程

前言 DNS协议作为着互联网客户端-服务器通信模式得第一关&#xff0c;在当下每天都有成千上亿上网记录产生得当今社会&#xff0c;其重要性自然不可言喻。在国内比较有名得DNS服务器有电信得114.114.114.114、阿里云得223.5.5.5&#xff0c;DNSPod得119.29.29.29&#xff0c;配…

【矩阵论】8. 常用矩阵总结——镜面阵,正定阵

8.4 镜面阵 法向量确定一个镜面 8.4.1 镜面阵的作用 对法向量 Aα−αA\alpha-\alphaAα−αA(Aα)A2ααA(A\alpha)A^2\alpha\alphaA(Aα)A2αα 对镜面上向量 AYYAYYAYY 8.4.2 镜面阵表示 AIn−2ααH∣α∣2,其中α(x1x2⋮xn)∈Cn,且α≠0AI_n-\frac{2\alpha\alpha^H}{\…

【实际开发02】- 同模块 - 单表 CRUD 代码 - 批量操作

目录 0. yml 配置 1. 账号 / 密码 等有概率变更的信息 推荐 动态配置 , 避免写死 1. entity 处理 ( 减少后续 insert/update 判空处理 ) 1. volidation.annotation 配合 Valid - 参数校验 2. Validated - ( 相较于 valid 更加严谨的校验 ) 1. Save / Status 2. Update /…

vue中使用axios

一、axios axios是一个基于Promise的网络请求库。既可以在node.js&#xff08;服务器端&#xff09;使用&#xff0c;也可以在浏览器端使用 1. 在node.js中使用的原生的http模块 2. 在浏览器中使用的XMLHttpRequest 二、vue中的使用方法 1. 安装&#xff1a;npm install axios…

二分查找及其扩展

目录 二分查找算法思想&#xff1a; 循环 递归 有多个与key相等数据的查询&#xff0c;找最左边与关键字相等的那个 找第一个大于key的元素的下标 有序循环数组的二分查找 二分查找算法思想&#xff1a; 二分查找也叫折半查找&#xff0c;查找效率较高。但是查找的线性表…