简介



线程,进程,协程基本概念不再赘述。

原生线程和用户线程

  1. 原生线程

    在内核态中创建的线程,只服务于内核态

  2. 用户线程

    由User Application创建的线程,该线程会在内核态与用户态中间来回穿梭

    比如Throw Exception,就会由CLR 线程触发,从用户态切换到内核态,再切换回用户态。

时钟中断与时间片

时钟中断的底层,是由主板上的硬件定时器产生,以固定的时间间隔(15.6ms)触发。windows作为消费端,来处理多线程任务调度/定时任务。

操作系统获取到中断后,再自行分配时间片,每个线程在一个时间片里获得CPU的运行时间,等时间片用完后,再由操作系统分配给下一个线程

windows 客户端一个时间片为 2个时钟中断 (15.62=31.5ms)

windows 服务端一个时间片为 12个时钟中断 (15.6
12=187.2ms,主要是为了更高的吞吐量)

CLR via C# 一文中说windows每30ms切换一次就是这个原因。

当一个线程时间片用完后,操作系统会将新的时间片转移给其它线程。以实现“多线程”效果。

单个核心在同一时间只能处理一个线程的任务。

眼见为实

  1. 中断多久触发一次?

    使用windbg进入内核态,使用nt!KeMaximumIncrement命令查看看它的值

注意,单位为100ns,因此156250*100/1000/1000=15.625ms

Windows下CPU核的数据结构

Windows会给每一个 CPU核 分配一个_KPCR的内存结构,用来记录当前CPU的状态。并拓展了_KPRCB来记录更多信息。

关键信息就是存储着 CuurentThread/NextThread/IdleThread(空闲线程)

眼见为实

使用dt命令来查看

dt命令是一个非常有用的显示类型信息的工具,主要用于查看和分析数据结构的布局和内容



CPU当前正在执行哪个线程?

使用!running命令,可以看到当前 CPU核 正在执行的线程

本质上就是对_KPCR/_KPRCB的提炼简化,

Windows下线程的数据结构

每个线程都有以下要素,这是创建线程无法避免的开销。

  1. 线程内核对象(Thread Kernel Object)

    OS中创建的每一个线程都会分配数据结构来承载描述信息

    Windows会给每一个 Thread 分配一个_ETHREAD的内存结构,用来记录当前线程的状态,其中就包括了线程上下文(Thread Context)

  2. 线程环境块(Thread Environment Block, TEB)

    TEB是在用户态中分配的内存块,主要包括线程的Exception,Local Storage等信息

  3. 用户态线程栈(User-Mode Stack)

    我们常说的栈空间就是指的这里,大名鼎鼎的OOM就出自于此

  4. 内核态线程栈(Kernel-Mode Stack)

    处于安全隔离考虑,在内核态中复制了一个同样的栈空间。用来处理用户态访问内核态的代码。

眼见为实

  1. 线程内核对象

    使用命令dt nt!_ETHREAD

  2. TEB

    使用命令dt nt!_TEB

线程上下文切换的本质

上下文切换的本质就是,备份被切换线程寄存器的值,到该线程的上下文中。再从切换后的线程中,读取上下文到寄存器中。

举个简单的例子就是,我跟你轮流打游戏,我玩的时候要先加载我的存档,轮到你玩的时候,我再保存我的存档。你玩的时候重复这一过程。

线程切换的成本

上下文切换是净开销,不会带来任何性能上的收益。因此优化程序的一个思路就是降低上下文切换

  1. 显式成本

    保存寄存器的值到内存,从内存读取寄存器。

    寄存器的数量越多成本就越高,以AMD 7840HS处理器为例,总共有17个寄存器

  2. 隐式成本

    如果线程切换是在同一个进程中,它们共享用户态的虚拟内存空间。所以当线程切换的时候,就有可能命中CPU的缓存(比如线程之间共享的变量,代码)。

    如果在不同的进程中,线程的切换则会导致用户态的虚拟内存空间都失效,进而导致CPU缓存失效。

眼见为实

说了这么多理论,不如直接看源码。

/*主代码入口*/
PUBLIC KiSwapContext
.PROC KiSwapContext /* Generate a KEXCEPTION_FRAME on the stack */
/* 核心逻辑:把寄存器全部备份一遍 */
GENERATE_EXCEPTION_FRAME /* Do the swap with the registers correctly setup */
/* 将新线程的地址,交换到R8寄存器上 */
mov r8, gs:[PcCurrentThread] /* Pointer to the new thread */
call KiSwapContextInternal /* Restore the registers from the KEXCEPTION_FRAME */
/* 把之前保存的寄存器值恢复到CPU寄存器 */
RESTORE_EXCEPTION_STATE /* Return */
ret
.ENDP MACRO(GENERATE_EXCEPTION_FRAME) /* Allocate a KEXCEPTION_FRAME on the stack */
/* -8 because the last field is the return address */
sub rsp, KEXCEPTION_FRAME_LENGTH - 8
.allocstack (KEXCEPTION_FRAME_LENGTH - 8) /* Save non-volatiles in KEXCEPTION_FRAME */
mov [rsp + ExRbp], rbp
.savereg rbp, ExRbp
mov [rsp + ExRbx], rbx
.savereg rbx, ExRbx
mov [rsp +ExRdi], rdi
.savereg rdi, ExRdi
mov [rsp + ExRsi], rsi
.savereg rsi, ExRsi
......省略
ENDM MACRO(RESTORE_EXCEPTION_STATE) /* Restore non-volatile registers */
mov rbp, [rsp + ExRbp]
mov rbx, [rsp + ExRbx]
mov rdi, [rsp + ExRdi]
mov rsi, [rsp + ExRsi]
mov r12, [rsp + ExR12]
mov r13, [rsp + ExR13]
mov r14, [rsp + ExR14]
mov r15, [rsp + ExR15]
movaps xmm6, [rsp + ExXmm6]
......省略 /* Clean stack and return */
add rsp, KEXCEPTION_FRAME_LENGTH - 8 ENDM

https://github.com/reactos/reactos/blob/master/ntoskrnl/ke/amd64/ctxswitch.S

线程调度模型(究极简化版)

在上面说到的逻辑核数据结构_KPRCB中,有三个属性。

单链表的DeferredReadyListHead,双链表的WaitListHead, 二维数组形态的DispatcherReadyListHead。

简单来说,当线程切换时,逻辑核从DispatcherReadyListHead根据线程优先级切换高优先级线程。如果线程主动放弃了时间片(thread.yield/thread.sleep),则会把线程放入DeferredReadyListHead。WaitListHead则用于存放那些正在等待某些事件发生的线程,如等待 I/O 操作完成、等待某个信号量或者等待互斥体等

眼见为实

直接看源码

可以看到DispatcherReadyListHead大小为32,主要是因为windows将线程优先级设为了0-31不同的级别。

https://github.com/reactos/reactos/blob/master/sdk/include/ndk/amd64/ketypes.h

C#线程结构模型

C#线程的底层是CLR托管线程,而CLR的承载是操作系统线程。因此它们都有一一对应的关系。



分别对应C#线程(Thread.CurrentThread.ManagedThreadId),CLR线程,OS线程

线程在创建过程中会经历两个阶段

        static void Main(string[] args)
{
var testThread = new Thread(DoWork);// 这个阶段只会在CLR中创建Thread,在OS上没有创建 testThread.Start();//CLR底层会调用系统api,创建OS线程
}

.NET Core 线程(Thread)底层原理浅谈的更多相关文章

  1. Java线上问题排查神器Arthas快速上手与原理浅谈

    前言 当你兴冲冲地开始运行自己的Java项目时,你是否遇到过如下问题: 程序在稳定运行了,可是实现的功能点了没反应. 为了修复Bug而上线的新版本,上线后发现Bug依然在,却想不通哪里有问题? 想到可 ...

  2. 线程池底层原理详解与源码分析(补充部分---ScheduledThreadPoolExecutor类分析)

    [1]前言 本篇幅是对 线程池底层原理详解与源码分析  的补充,默认你已经看完了上一篇对ThreadPoolExecutor类有了足够的了解. [2]ScheduledThreadPoolExecut ...

  3. CSRF漏洞原理浅谈

    CSRF漏洞原理浅谈 By : Mirror王宇阳 E-mail : mirrorwangyuyang@gmail.com 笔者并未深挖过CSRF,内容居多是参考<Web安全深度剖析>.& ...

  4. 如何把Java代码玩出花?JVM Sandbox入门教程与原理浅谈

    在日常业务代码开发中,我们经常接触到AOP,比如熟知的Spring AOP.我们用它来做业务切面,比如登录校验,日志记录,性能监控,全局过滤器等.但Spring AOP有一个局限性,并不是所有的类都托 ...

  5. JAVA CAS原理浅谈

    java.util.concurrent包完全建立在CAS之上的,没有CAS就不会有此包.可见CAS的重要性. CAS CAS:Compare and Swap, 翻译成比较并交换. java.uti ...

  6. Java中的SPI原理浅谈

    在面向对象的程序设计中,模块之间交互采用接口编程,通常情况下调用方不需要知道被调用方的内部实现细节,因为一旦涉及到了具体实现,如果需要换一种实现就需要修改代码,这违反了程序设计的"开闭原则& ...

  7. CAS+SSO原理浅谈

    http://www.cnblogs.com/yonsin/archive/2009/08/29/1556423.htmlSSO 是一个非常大的主题,我对这个主题有着深深的感受,自从广州 UserGr ...

  8. Mysql锁原理浅谈

    锁类型/引擎 行锁 表锁 页锁 MyISAM 有 InnoDB 有 有 BDB(被InnoDB取代) 有 有 锁的分类 表锁:开销小,加锁快,不会死锁,粒度大,冲突率高,并发低. 行锁:开销大,加锁慢 ...

  9. php模板原理PHP模板引擎smarty模板原理浅谈

    mvc是开发中的一个伟大的思想,使得开发代码有了更加清晰的层次,让代码分为了三层各施其职.无论是对代码的编写以及后期的阅读和维护,都提供了很大的便利. 我们在php开发中,视图层view是不允许有ph ...

  10. PHP的模板引擎smarty原理浅谈

    mvc是开发中的一个伟大的思想,使得开发代码有了更加清晰的层次,让代码分为了三层各施其职.无论是对代码的编写以及后期的阅读和维护,都提供了很大的便利. 我们在php开发中,视图层view是不允许有ph ...

随机推荐

  1. Unix、Linux、GNU 关系梳理

    之前写了一篇 MSYS2.MinGW 和 Cygwin 关系梳理的博客,但是要讲清它们几个的关系最好还是先了解一下操作系统的发展历程.遂补充了这篇博客. UNIX:现代操作系统的始祖 Operatin ...

  2. Go make 介绍

    Go 语言中的 make 函数用于创建和初始化特定类型的对象,主要是用于创建切片(slice).映射(map)和通道(channel).make 函数与 new 函数不同,new 函数是用于分配内存, ...

  3. 如何选择 Linux 发行版

    简介 要建立云服务器,首先需要安装操作系统.在现代环境中,几乎所有情况下都是指 Linux 操作系统.从历史上看,Windows 服务器和其他类型的 Unix 在特定的商业环境中都很流行,但现在几乎每 ...

  4. HttpContext.SignInAsync 失效(表面解决了问题,未深入到.net core 源码去找问题,记录一下,等有时间翻一下.net core 源码试试能不能找到根本原因)

    今天在弄 identityServer4 项目的时候,发现好好的登录竟然没用了. 各种跟踪后发现是 HttpContext.SignInAsync 这个方法不写cookies了 原本经过这个方法后,会 ...

  5. R-Adapter:零样本模型微调新突破,提升鲁棒性与泛化能力 | ECCV 2024

    大规模图像-文本预训练模型实现了零样本分类,并在不同数据分布下提供了一致的准确性.然而,这些模型在下游任务中通常需要微调优化,这会降低对于超出分布范围的数据的泛化能力,并需要大量的计算资源.论文提出新 ...

  6. 补: Rest 风格请求处理的的内容补充(1)

    补: Rest 风格请求处理的的内容补充(1) Rest风格请求:注意事项和细节 客户端是PostMan 可以直接发送Put,delete等方式请求,可不设置Filter 如果哟啊SpringBoot ...

  7. Asp.net Core – CSS Isolation

    前言 ASP.NET Core 6.0 Razor Pages 新功能, 我是用 webpack 做打包的, 所以这个对我没有什么帮助. 但是了解一下是可以的. 希望 .NET 会继续发展的更好, 多 ...

  8. OData – How It Work

    前言 OData 是很冷门的东西, 用的人少, 开发的人少, 文档自然也少的可怜. 如果真的想用它, 多少要对它机制有点了解. 这样遇到 bug, 想扩展的时候才不至于完全没有路. 主要参考: ODa ...

  9. Flutter 3.3 正式发布

    Flutter 3 是我们正式为全平台提供支持的一个重量级里程碑,距离它的发布仅过去了三个月,今天让我们有请 Flutter 3.3 正式版!近三个月我们并没有放慢更新迭代的速度--自 Flutter ...

  10. 仿函数(Functor)是什么?

    仿函数(Functor) 仿函数是通过重载()运算符的类或结构体的对象.这样一个对象可以像普通函数一样被调用. 仿函数通常用于需要在对象内部保留状态或多次调用时有特定行为的情况. 特点: 仿函数是一个 ...