秒懂Linux之线程

fe594ea5bf754ddbb223a54d8fb1e7bc.gif 

目录

线程概念

线程理解

地址空间(页表,内存,虚拟地址)

线程的控制

 铺垫

线程创建

​编辑

线程等待

线程异常

线程终止

代码

线程优点

线程缺点

线程特点


 

线程概念

线程是进程内部的一个执行分支,线程是CPU调度的基本单位~

以前我们学过:加载到内存的程序称为进程,并且还修正了进程的概念——内核数据结构+进程代码与数据~

在没有学习线程前我们的正文代码执行都是串行调用的,如果能做到并行调用会提高效率,而这就是我们学习线程的原因~

线程理解

通过上图我们是可以了解到创建一个进程的成本是很高的~

那假设我们把正文代码分为3份,创建进程(执行流)时不去创建页表,地址空间,让新出现的两个PCB共同指向第一个进程的地址空间,三个进程各自执行正文代码的各部分区域,达到并行的目的~

地址空间与地址空间上的虚拟地址,本质上也是一种资源~

如果我们想要设计线程,那就必须要让OS进行管理——先描述,再组织~

可是线程和进程有太多类似的地方了,创建的成本开销也大,所以在Linux下是没有真正的线程的,反而是用我们上面所想的那般以进程模拟线程~

这就是为什么CPU是以线程为调度单位的原因,以前我们都是觉得是以进程为调度单位的,那是因为里面只有一个执行流,而现在学习线程后就可以理解多个执行流被cpu所调度。

所以我们重新认识一下什么是进程(内核角度)——承担分配系统资源的基本实体~

我们也不用去刻意区分被调度的task_struct到底是线程还是进程,统一把其当作执行流~也叫做轻量级进程~

地址空间(页表,内存,虚拟地址)

我们先提出一个问题:OS要不要管理内存呢?——要的~

如上图所示OS把内存以4KB的数据块进行划分,我们的程序文件恰好也是以4KB的数据块划分的,这样内存与程序能达到以数据库相互对应的目的~而我们把这些4KB大小的数据块由称为页框/页帧~

我们把页框进行描述,里面存放各自属性的数据。假设内存大小为4GB,那么就可以划分1048576个4KB大小的页框,然后以数组的形式组织起来~

因此可以得证一件事:OS进行内存管理的基本单位为4KB~

我们熟悉内存管理后再回来深度理解多个执行流是如何进行代码划分的~毕竟总不能我们臆想就划分成功的吧?

首先页表肯定不是如此简单地把物理地址与虚拟地址放在一起就说明映射的,地址空间有4G(2^32)而页表如果要映射地址空间中的每个虚拟地址,那么在一行的映射中是要包含物理地址,虚拟地址,权限等数据的,假设这些大小为10byte,那一个页表岂不是要2^32 *10这么大?这成本也是不合实际的~

所以真正的页表是下面这种~

我们把虚拟地址划分为3个区域,其中前10位表示页目录,一共可以存放1024个数据~

中间10位表示页表项,每个页表都可以存放1024个数据~

欧克,我们再来梳理一遍~前面10位用来寻找在页目录中对应的页表项(即在哪个页表),中间10位用来查找  在对应页表内的哪一个数据,里面存放着程序代码所属代码块的物理地址(页框的物理地址)最后12位是偏移量,通过里面存放的物理地址+该偏移量就能找到在对应数据块内部真正代码的位置(页内偏移)~ 然后在页表添上权限,与cpu一同判定为内核态还是用户态~

而这才是真正的页表映射~

给不同线程分配不同的区域,本质上就是让不同的线程各自看到全部页表的子集~

线程的控制

 铺垫

Linux下只有轻量级进程而没有线程是为了减少开发成本,而为了给用户提供线程是引进了线程库供我们使用。 

线程创建

test_thread:testThread.ccg++ -o $@ $^ -lpthread -std=c++11
.PHONY:clean
clean:rm -f test_thread
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>void *newthreadrun(void *args)
{while (true){std::cout << "I am new thread, pid: " << getpid() << std::endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthreadrun, nullptr);while (true){std::cout << "I am main thread, pid: " << getpid() << std::endl;sleep(1);}
}

在同一程序下,进行了并行调用~

另外这里我们看到了一个新东西:LWP(轻量级进程),而操作系统在进行调度的时候是以LWP进行的,以前只有一个执行流时那么LWP==PID~

tid用于在库中维护,类似前面信号所学的mid,而LWP是用于内核中标识线程的,类似于key~

下面我们再来介绍一个函数:pthread_self(用来查看当前线程的tid)

//把地址转16进制
string ToHex(pthread_t tid)
{char id [64];snprintf(id,sizeof(id),"0x%lx",tid);return id;
}void *newthreadrun(void *args)
{while (true){cout << "I am new thread, pid: " << getpid() << "  ToHex(tid):"<<ToHex(pthread_self())<<endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthreadrun, nullptr);while (true){cout << "I am main thread, pid: " << getpid() << "new thread tid: "<<ToHex(tid)<<"main thread tid"<<ToHex(pthread_self())<<endl;sleep(1);}
}

这里也验证了新线程确实是在主线程这里被创建的~

ps:关于新线程与主线程谁先运行这是不确定的,由调度器决定~

最后我们再来做个小demo:让主线程先退出,查看新线程情况~

//把地址转16进制
string ToHex(pthread_t tid)
{char id [64];snprintf(id,sizeof(id),"0x%lx",tid);return id;
}void *newthreadrun(void *args)
{string threadname = (char*)args;int cnt = 5;while (cnt){cout << threadname << " is running: " << cnt << ", pid: " << getpid() << "  ToHex(tid):"<<ToHex(pthread_self())<<endl;sleep(1);cnt--;}return nullptr;
}int main()
{pthread_t tid;//将字符串"thread-1"传递给形参argspthread_create(&tid, nullptr, newthreadrun, (void*)"thread-1");//主线程提前退出cout<<"main thread quit"<<endl;return 0;
}

由此验证一件事:主线程退出==进程退出==所有线程都要退出 

线程等待

线程退出也是需要”wait“的,否则就会和进程那样发送内存泄漏的情况~

功能:等待线程结束
原型
int pthread_join ( pthread_t thread , void ** value_ptr );
参数
thread : 线程 ID
value_ptr : 它指向一个指针,后者指向线程的返回值
返回值:成功返回 0 ;失败返回错误码
调用该函数的线程将挂起等待,直到idthread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的
终止状态是不同的,总结如下 :
1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED。
3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

 

void *newthreadrun(void *args)
{string threadname = (char*)args;int cnt = 5;while (cnt){cout << threadname << " is running: " << cnt << ", pid: " << getpid() << "  ToHex(tid):"<<ToHex(pthread_self())<<endl;sleep(1);cnt--;}return nullptr;
}int main()
{pthread_t tid;//将字符串"thread-1"传递给形参argspthread_create(&tid, nullptr, newthreadrun, (void*)"thread-1");int n = pthread_join(tid,nullptr);cout<<"main thread quit, n="<<n<<endl;sleep(5);return 0;
}

回收成功~

 

void *newthreadrun(void *args)
{string threadname = (char*)args;int cnt = 5;while (cnt){cout << threadname << " is running: " << cnt << ", pid: " << getpid() << "  ToHex(tid):"<<ToHex(pthread_self())<<endl;sleep(1);cnt--;}return (void*)123;
}int main()
{pthread_t tid;//将字符串"thread-1"传递给形参argspthread_create(&tid, nullptr, newthreadrun, (void*)"thread-1");//线程回收void* ret = nullptr;int n = pthread_join(tid,&ret);cout<<"main thread quit, n="<<n<<"  ret = "<<(long long)ret<<endl;sleep(5);return 0;
}

回收同时取得线程的返回值~ 

线程异常

线程退出有以下三种结果:

  • 代码跑完,结果正确
  • 代码跑完,结果错误
  • 出现异常

而我们重点放在异常上面~


void *newthreadrun(void *args)
{string threadname = (char*)args;int cnt = 5;while (cnt){cout << threadname << " is running: " << cnt << ", pid: " << getpid() << "  ToHex(tid):"<<ToHex(pthread_self())<<endl;sleep(1);cnt--;//人为造异常int*p = nullptr;*p =1;}return (void*)123;
}int main()
{pthread_t tid;//将字符串"thread-1"传递给形参argspthread_create(&tid, nullptr, newthreadrun, (void*)"thread-1");//测试异常while(true){sleep(1);}//线程回收void* ret = nullptr;int n = pthread_join(tid,&ret);cout<<"main thread quit, n="<<n<<"  ret = "<<(long long)ret<<endl;sleep(5);return 0;
}

我们发现当新线程出现异常后,连主线程都没了。

重点:在多线程中任意一个线程出现异常,都会导致整个进程退出!这也意味着线程的健壮性不是很好。并且我们还注意到因为线程异常我们连其返回值都接收不到了,因为异常导致所有线程都退出,是走不到回收函数的,整个进程都崩溃了,所以异常时往往不考虑回收了~

线程终止

如果需要只终止某个线程而不终止整个进程 , 可以有三种方法 :
  • 从线程函数return。这种方法对主线程不适用,main函数return相当于调用exit
  •  线程可以调用pthread_ exit终止自己。
  • 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

pthread_exit函数  

功能:线程终止
原型
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr 不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
void *newthreadrun(void *args)
{string threadname = (char*)args;int cnt = 5;while (cnt){cout << threadname << " is running: " << cnt << ", pid: " << getpid() << "  ToHex(tid):"<<ToHex(pthread_self())<<endl;sleep(1);cnt--;}pthread_exit ((void*)123);
}int main()
{pthread_t tid;//将字符串"thread-1"传递给形参argspthread_create(&tid, nullptr, newthreadrun, (void*)"thread-1");//线程回收void* ret = nullptr;int n = pthread_join(tid,&ret);cout<<"main thread quit, n="<<n<<"  ret = "<<(long long)ret<<endl;sleep(5);return 0;
}

 

pthread_cancel函数
功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
参数
thread: 线程 ID
返回值:成功返回 0 ;失败返回错误码
int main()
{pthread_t tid;//将字符串"thread-1"传递给形参argspthread_create(&tid, nullptr, newthreadrun, (void*)"thread-1");//2s后把新线程终止sleep(2);pthread_cancel(tid);//线程回收void* ret = nullptr;int n = pthread_join(tid,&ret);cout<<"main thread quit, n="<<n<<"  ret = "<<(long long)ret<<endl;sleep(5);return 0;
}

 

线程优点

最重要的是前2点:其中第一点不用我们多说,由于线程共享地址空间的缘故它们可以共享大部分资源~

再来谈谈第二点~

我们的认知是进程切换时需要在CPU中的寄存器重新记录数据~

 

后面有了cache后直接在这里取数据更便捷了,但归根到底进程切换还是得考虑cache的记录,因为每一次进程都是不一样的。不过线程就不需要顾虑太多了,它切换后cache概率上还能用~

线程缺点

线程特点

线程私有

  • 线程的硬件上下文(CPU寄存器的值)
  • 线程的独立栈结构

线程公有

  • 代码和全局数据
  • 进程文件描述符

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

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

相关文章

云服务器部署k8s需要什么配置?

云服务器部署k8s需要什么配置&#xff1f;云服务器部署K8s需要至少2核CPU、4GB内存、50GBSSD存储的主节点用于管理集群&#xff0c;工作节点建议至少2核CPU、2GB内存、20GBSSD。还需安装Docker&#xff0c;选择兼容的Kubernetes版本&#xff0c;配置网络插件&#xff0c;以及确…

客运自助售票系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;乘客管理&#xff0c;司机管理&#xff0c;车票信息管理&#xff0c;订单信息管理&#xff0c;退票信息管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;车票信息&#…

JSON的C实现(上)

JSON的C实现&#xff08;上&#xff09; JSON的C实现&#xff08;上&#xff09;前言JSON简介JSON的C实现思路小结 JSON的C实现&#xff08;上&#xff09; 前言 JSON是众多项目中较为常见的数据交换格式&#xff0c;为不同项目、系统间的信息交换提供了一个规范化标准。JSON…

SpringBoot3+Vue3开发后台管理系统脚手架

后台管理系统脚手架 介绍 在快速迭代的软件开发世界里&#xff0c;时间就是生产力&#xff0c;效率决定成败。对于构建复杂而庞大的后台系统而言&#xff0c;一个高效、可定制的后台脚手架&#xff08;Backend Scaffold&#xff09;无疑是开发者的得力助手。 脚手架 后台脚…

从0到1深入浅出构建Nest.Js项目

Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用JavaScript 的渐进增强的能力&#xff0c;使用并完全支持 TypeScript &#xff08;仍然允许开发者使用纯 JavaScript 进行开发&#xff09;&#xff0c;并结合了 OOP &#xff08;面向对…

【Docker】docker的存储

介绍 docker存储主要是涉及到3个方面&#xff1a; 第一个是容器启动时需要的镜像 镜像文件都是基于图层存储驱动来实现的&#xff0c;镜像图层都是只读层&#xff0c; 第二个是&#xff1a; 容器读写层&#xff0c; 容器启动后&#xff0c;docker会基于容器镜像的读层&…

服务器数据恢复—raid磁盘故障导致数据库文件损坏的数据恢复案例

服务器存储数据恢复环境&故障&#xff1a; 存储中有一组由3块SAS硬盘组建的raid。上层win server操作系统层面划分了3个分区&#xff0c;数据库存放在D分区&#xff0c;备份存放在E分区。 RAID中一块硬盘的指示灯亮红色&#xff0c;D分区无法识别&#xff1b;E分区可识别&a…

【理论科学与实践技术】数学与经济管理中的学科与实用算法

在现代商业环境中&#xff0c;数学与经济管理的结合为企业提供了强大的决策支持。包含一些主要学科&#xff0c;包括数学基础、经济学模型、管理学及风险管理&#xff0c;相关的实用算法和这些算法在中国及全球知名企业中的实际应用。 一、数学基础 1). 发现人及著名学者 发…

开源项目 - 交通工具检测 yolo v3 物体检测 单车检测 车辆检测 飞机检测 火车检测 船只检测

开源项目 - 交通工具检测 yolo v3 物体检测 单车检测 车辆检测 飞机检测 火车检测 船只检测 开源项目地址&#xff1a;https://gitcode.net/EricLee/yolo_v3 示例&#xff1a;

前端学习第二天笔记 CSS选择 盒子模型 浮动 定位 CSS3新特性 动画 媒体查询 精灵图雪碧图 字体图标

CSS学习 CSS选择器全局选择器元素选择器类选择器ID选择器合并选择器 选择器的优先级字体属性背景属性文本属性表格属性表格边框折叠边框表格文字对齐表格填充表格颜色 关系选择器后代选择器子代选择器相邻兄弟选择器通用兄弟选择器 CSS盒子模型弹性盒子模型父元素上的属性flex-…

大厂面试真题-说一下Mybatis的缓存

首先看一下原理图 Mybatis提供了两种缓存机制&#xff1a;一级缓存&#xff08;L1 Cache&#xff09;和二级缓存&#xff08;L2 Cache&#xff09;&#xff0c;旨在提高数据库查询的性能&#xff0c;减少数据库的访问次数。注意查询的顺序是先二级缓存&#xff0c;再一级缓存。…

主存储器——随机存取存储器RAM

静态RAM 双稳态触发器 一、工作特性 两种稳定状态&#xff1a; 双稳态触发器具有两个稳定的输出状态&#xff0c;通常表示为 0 和 1&#xff08;或低电平和高电平&#xff09;。这两个状态可以长期保持&#xff0c;即使在没有输入信号的情况下&#xff0c;也不会自发地改变。 例…

初识TCP/IP协议

回顾上文 来回顾一下TCP协议的特性&#xff0c;有一道比较经典的题&#xff1a;如何使用UDP实现可靠传输&#xff0c;通过应用程序的代码&#xff0c;完成可靠传输的过程&#xff1f; 原则&#xff0c;TCO有啥就吹啥&#xff0c;引入滑动窗口&#xff0c;引入流量控制&#x…

基于 Qwen2.5-0.5B 微调训练 Ner 命名实体识别任务

一、Qwen2.5 & 数据集 Qwen2.5 是 Qwen 大型语言模型的最新系列&#xff0c;参数范围从 0.5B 到 72B 不等。 对比 Qwen2 最新的 Qwen2.5 进行了以下改进&#xff1a; 知识明显增加&#xff0c;并且大大提高了编码和数学能力。在指令跟随、生成长文本&#xff08;超过 8K…

前台项目启动/打包报错 Error: error:0308010C:digital envelope routines::unsupported

在package.json中修改启动/打包语句 如图&#xff0c;我这里是打包时候报错&#xff0c;就在build里前面加上 set NODE_OPTIONS--openssl-legacy-provider && 再次打包&#xff0c;成功。

Unity 2D RPG Kit 学习笔记

学习资料&#xff1a; B站教学视频&#xff1a;https://www.bilibili.com/video/BV1dC4y1o7A5?p1&vd_source707ec8983cc32e6e065d5496a7f79ee6 2D RPG Kit Documentation.pdf文档 1、2D RPG Kit Documentation文档 1.1、Scenes/TitleScreen 开始菜单工程 1.2、https://it…

闭源与开源嵌入模型比较以及提升语义搜索效果的技术探讨

上图为执行语义搜索前的聚类演示 &#xff0c;嵌入技术是自然语言处理的核心组成部分。虽然嵌入技术的应用范围广泛&#xff0c;但在检索应用中的语义搜索仍是其最常见的用途之一。 尽管知识图谱等可以提升检索的准确率和效率&#xff0c;但标准向量检索技术仍然具有其实用价值…

「安装」 Windows下安装CUDA和Pytorch

「安装」 Windows下安装CUDA和Pytorch 文章目录 「安装」 Windows下安装CUDA和PytorchMac、Linux、云端Windows安装CUDA安装miniconda安装PyTorch测试总结 其他 Mac、Linux、云端 Mac、Linux、云端安装Miniconda和Pytorch的方法参考其他资料。 Windows 下面进行Windows下安装…

TDengine 流计算与窗口机制的深度解析:揭示计数窗口的关键作用

在 TDengine 3.2.3.0 版本中&#xff0c;我们针对流式计算新增了计数窗口&#xff0c;进一步优化了流式数据处理的能力。本文将为大家解读流式计算与几大窗口的关系&#xff0c;并针对新增的计数窗口进行详细的介绍&#xff0c;帮助大家进一步了解 TDengine 流式计算&#xff0…