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

linux yield_带你通俗易懂的了解——Linux线程模型和线程切换

本文从linux中的进程、线程实现原理开始,扩展到linux线程模型,最后简单解释线程切换的成本。

linux中的进程与线程

首先明确进程与进程的基本概念:

  • 进程是资源分配的基本单位
  • 线程是CPU调度的基本单位
  • 一个进程下可能有多个线程
  • 线程共享进程的资源

基本原理

linux用户态的进程、线程基本满足上述概念,但内核态不区分进程和线程。可以认为,内核中统一执行的是进程,但有些是“普通进程”(对应进程process),有些是“轻量级进程”(对应线程pthread或npthread),都使用task_struct结构体保存保存。

使用fork创建进程,使用pthread_create创建线程。两个系统调用最终都都调用了do_dork,而do_dork完成了task_struct结构体的复制,并将新的进程加入内核调度。

进程是资源分配的基本单位、线程共享进程的资源

普通进程需要深拷贝虚拟内存、文件描述符、信号处理等;而轻量级进程之所以“轻量”,是因为其只需要浅拷贝虚拟内存等大部分信息,多个轻量级进程共享一个进程的资源。

线程是CPU调度的基本单位、一个进程下可能有多个线程

linux加入了线程组的概念,让原有“进程”对应线程,“线程组”对应进程,实现“一个进程下可能有多个线程”:

  • 操作系统中存在多个进程组
  • 一个进程组下有多个进程(1:n)
  • 一个进程对应一个线程组(1:1)
  • 一个线程组下有多个线程(1:n)

task_struct中,使用pgid标的进程组,tgid标的线程组,pid标的进程或线程。假设目前有一个进程组,则上述概念对应如下:

  • 进程组中有一个主进程(父进程),pid等于进程组的pgid;进程组下的其他进程都是父进程的子进程,pid不等于pgid
  • 每个进程对应一个线程组,pid等于tgid。
  • 线程组中有一个“主线程”(勉强称为“主线程”,位的是与主进程对应;语义上绝不能称为“父线程”),pid等于该线程组的tgid;线程组下的其他线程都是与主线程平级,pid不等于tgid

因此,调用getpgid返回pgid,调用getpid应返回tgid,调用gettid应返回pid。使用的时候不要糊涂。

进程下除主线程外的其他线程是CPU调度的基本单位,这很好理解。而所谓主线程与所属进程实际上是同一个task_struct,也能被CPU调度,因此主线程也是CPU调度的基本单位。

tgid相同的所有线程组成了概念上的“进程”,只有主线程在创建时会实际分配资源,其他线程通过浅拷贝共享主线程的资源。结合前面介绍的普通线程与轻量级进程,实现“进程是资源分配的基本单位”。

举个栗子

c3600c83b38fe15fa1d8af6b851d7da7.png
  • 存在3个进程组111、112、113
  • 进程组111下有1个父进程111,单独分配资源
  • 进程111下有1个线程111,共享进程111的资源
  • 进程组112下有1个父进程112,单独分配资源
  • 进程112下有2个线程112、113,共享进程112的资源
  • 进程组113下有1个父进程113,1个子进程115,各自单独分配资源
  • 进程113下有2个线程113、114,共享进程113的资源
  • 进程115下有3个线程115、116、117,共享进程115的资源

小结

现在再来理解linux中的进程与线程就容易多了:

  • 进程是一个逻辑上的概念,用于管理资源,对应task_struct中的资源
  • 每个进程至少有一个线程,用于具体的执行,对应task_struct中的任务调度信息
  • 以task_struct中的pid区分线程,tgid区分进程,pgid区分进程组

linux线程模型

一对一

LinuxThreads与NPTL均采用一对一的线程模型,一个用户线程对应一个内核线程。内核负责每个线程的调度,可以调度到其他处理器上面。Linux 2.6默认使用NPTL线程库,一对一的线程模型

优点:

  • 实现简单。

缺点:

  • 对用户线程的大部分操作都会映射到内核线程上,引起用户态和内核态的频繁切换。
  • 内核为每个线程都映射调度实体,如果系统出现大量线程,会对系统性能有影响。

多对一

顾名思义,多对一线程模型中,多个用户线程对应到同一个内核线程上,线程的创建、调度、同步的所有细节全部由进程的用户空间线程库来处理。

优点:

  • 用户线程的很多操作对内核来说都是透明的,不需要用户态和内核态的频繁切换。使线程的创建、调度、同步等非常快。

缺点:

  • 由于多个用户线程对应到同一个内核线程,如果其中一个用户线程阻塞,那么该其他用户线程也无法执行。
  • 内核并不知道用户态有哪些线程,无法像内核线程一样实现较完整的调度、优先级等

多对多

多对一线程模型是非常轻量的,问题在于多个用户线程对应到固定的一个内核线程。多对多线程模型解决了这一问题:m个用户线程对应到n个内核线程上,通常m>n。由IBM主导的NGPT采用了多对多的线程模型,不过现在已废弃。

优点:

  • 兼具多对一模型的轻量
  • 由于对应了多个内核线程,则一个用户线程阻塞时,其他用户线程仍然可以执行
  • 由于对应了多个内核线程,则可以实现较完整的调度、优先级等

缺点:

  • 实现复杂

线程切换

linux采用一对一的线程模型,用户线程切换与内核线程切换之间的差别非常小。同时,如果忽略用户主动放弃用户线程的执行权(yield)带来的开销,则只需要考虑内核线程切换的开销。

注意,这里仅仅是为了帮助理解做出的简化。实际上,用户线程库在用户线程的调度、同步等过程中做了很多工作,这部分开销不能忽略

如JVM对Thread#yield()的解释:如果底层OS不支持yield的语义,则JVM让用户线程自旋至时间片结束,线程被动切换,以达到相似的效果。

什么引起线程切换

  • 时间片轮转
  • 线程阻塞
  • 线程主动放弃时间片

线程切换的开销

直接开销

直接开销是线程切换本身引起的,无可避免,必然发生。

用户态与内核态的切换

线程切换只能在内核态完成,如果当前用户处于用户态,则必然引起用户态与内核态的切换。(“用户态与内核态的切换”具体带来什么成本???)

上下文切换

前面说线程(或者叫做进程都随意)信息需要用一个task_struct保存,线程切换时,必然需要将旧线程的task_struct从内核切出,将新线程的切入,带来上下文切换。除此之外,还需要切换寄存器、程序计数器、线程栈(包括操作栈、数据栈)等。

线程调度算法

线程调度算法需要管理线程的状态、等待条件等,如果根据优先级调度,则还需要维护优先级队列。如果线程切换比较频繁,该成本不容小觑。

间接开销

间接开销是直接开销的副作用,取决于系统实现和用户代码实现。

缓存缺失

切换进程,需要执行新逻辑。如果二者的访问的地址空间不相近,则会引起缓存缺失,具体影响范围取决于系统实现和用户代码实现。如果系统的缓存较大,则能减小缓存缺失的影响;如果用户线程访问数据的地址空间接近,则本身的缓存缺失率也比较低。

对页表等快慢表式结构同理。

68f8d239a5acd6cf62f255eafe70fbe4.png

带你了解必知必会的线程原理和模型点击即可观看!

另外关于c++ Linux后台服务器开发的一些知识点分享:Linux,Nginx,MySQL,Redis,P2P,K8S,Docker,TCP/IP,协程,DPDK,webrtc,音视频等等视频。

喜欢的朋友可以后台私信【1】获取学习视频

57f2de5dc41a098c0f696c0342e47940.png
e3419752f0864646d2cd7d58252c1cbf.png

相关文章:

  • python中找到第一个符合的数据点-python – 从满足条件的NumPy矩阵中的每一行中取N个第一个值...
  • 系统试图将驱动器合并到合并驱动器上的目录_四川宜宾伦茨伺服驱动器【兴百川】...
  • 精准解决贫困地区_越秀 | 党建引领,精准扶贫+特色农产品展销,这个展销厅开业当天很火爆...
  • arduinouno的地是相连的吗_可移动二次结构柱混凝土浇筑泵效果好吗?
  • db2 replace函数的用法_python基础系列——字符串知识点,函数及操作
  • cf两边黑屏怎么解决win10_电脑黑屏怎么解决
  • docker 删除包含关键字的镜像_手摸手带你 Docker 从入门到实践
  • SSD浅层网络_【CV中的特征金字塔】三,两阶段实时检测网络ThunderNet
  • python requests 模拟浏览器_Python requests库模拟浏览器行为的一些技巧记录
  • torch 变量_[错误汇总]torch.load加载模型报错;cuda数据类型报错
  • python编写石头剪刀布_用Python编写石头剪刀游戏,python,一个,布,的
  • python工具包_Python进阶_python常用工具包(1)
  • postman如何改成中文版_刚入行的测试工程师如何自学软件测试?
  • jwt认证机制优势和原理_Node.js 使用 express-jwt 解析 JWT
  • php 数组从后向前查找_数组内元素顺移
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • Angular4 模板式表单用法以及验证
  • Docker 笔记(1):介绍、镜像、容器及其基本操作
  • java8 Stream Pipelines 浅析
  • JAVA多线程机制解析-volatilesynchronized
  • mysql 数据库四种事务隔离级别
  • nodejs实现webservice问题总结
  • React Transition Group -- Transition 组件
  • Vue ES6 Jade Scss Webpack Gulp
  • 百度地图API标注+时间轴组件
  • 如何在 Tornado 中实现 Middleware
  • 什么软件可以剪辑音乐?
  • 适配iPhoneX、iPhoneXs、iPhoneXs Max、iPhoneXr 屏幕尺寸及安全区域
  • 正则学习笔记
  • 《天龙八部3D》Unity技术方案揭秘
  • 关于Kubernetes Dashboard漏洞CVE-2018-18264的修复公告
  • ​比特币大跌的 2 个原因
  • ​人工智能书单(数学基础篇)
  • #我与Java虚拟机的故事#连载16:打开Java世界大门的钥匙
  • (1)(1.19) TeraRanger One/EVO测距仪
  • (更新)A股上市公司华证ESG评级得分稳健性校验ESG得分年均值中位数(2009-2023年.12)
  • (转载)深入super,看Python如何解决钻石继承难题
  • .NET 命令行参数包含应用程序路径吗?
  • .net遍历html中全部的中文,ASP.NET中遍历页面的所有button控件
  • .NET和.COM和.CN域名区别
  • .NET序列化 serializable,反序列化
  • .net中应用SQL缓存(实例使用)
  • .one4-V-XXXXXXXX勒索病毒数据怎么处理|数据解密恢复
  • @Not - Empty-Null-Blank
  • [AIGC codze] Kafka 的 rebalance 机制
  • [ANT] 项目中应用ANT
  • [BZOJ1008][HNOI2008]越狱
  • [c++] C++多态(虚函数和虚继承)
  • [C++] Windows中字符串函数的种类
  • [C++][数据结构][算法]单链式结构的深拷贝
  • [IE编程] 如何获得IE版本号
  • [Java][Liferay] File system in liferay
  • [LeetCode]-225. 用队列实现栈-232. 用栈实现队列
  • [LeetCode系列]3元素最近和问题的O(n^2)解法
  • [Linux打怪升级之路]-vim编辑器(看就能马上操作噢)