0%

《Golang》Golang的优势

最近看到网上很多人不理解,为什么Golang会效率高,为什么Golang会备受青睐

我想表达一下我的理解

借鉴内核的调度思路

我在学习完Golang的调度之后,意外的发现,它的思路跟操作系统对线程的调度惊奇的相似,我想Golang的开发团队一定是有借鉴的吧

来看看他们的相似之处:

多处理器相似之处

在CPU单核的时代,操作系统不需要管理多个核的分配。到了CPU多核的时代,操作系统必须进化成多核操作系统

需要负责多核的负载均衡,不能让某个核一直处于空闲状态

做法大致就是:每个核对应有一个本地的运行队列,还有一个全局的运行队列,当某个核上的队列中的线程都跑完了,每1ms,它会使用系统调用到最忙核上获得任意一个任务执行;而在全部处理器都有任务在执行时,则由时钟每200ms唤起系统调用去检查,若发现在Linux标准下不均衡,则会发生处理器之间的就绪任务迁移

下面是类比关系

Golang中概念 类似于操作系统中的 实际是
Go运行时 操作系统内核 一个运行库,类似C运行库
g 线程 Go运行时抽象出来的协程
p CPU中的真实处理器 逻辑处理器
G的窃取 内核中线程的迁移 G的窃取
g栈 线程的栈 go运行时的堆管理器分配的一段虚拟空间
g0栈 每个处理器的栈 线程的栈
gsignal栈 信号处理逻辑的栈
g0 每个处理器执行的内核 每个参与调度的m执行的调度器
g态 用户态 正在执行协程中的代码
g0态 内核态 正在执行Go运行时中的代码
内存 从操作系统申请的虚拟内存空间
mutex锁 线程锁Binary Semaphore Go实现的锁
m CPU中的真实处理器 线程

Golang中的M就是为P提供计算资源的,因为Golang中的P是逻辑上的概念,并没有真实的计算能力

g存在running的状态,进程同样也存在类似的状态

Golang中为信号处理分配了单独的栈,接收到信号后,会切换到gsignal栈执行信号处理函数

Golang中m的数量可以比p的数量大,而Linux中真实处理器的数量一定等于调度器的数量,也不会腾出一个核出来充当sysmon,这样太奢侈了

注意:本文有些概念是作者为方便表达而写出来的

调度切换相似之处

操作系统是通过安装中断处理函数,其他设备向CPU发出硬中断或者用户进程向内核发出软中断,来进行用户态向内核态切换的

用户态向内核态切换后,内核就掌握了执行权,就可以决定下一步该哪个线程执行了

但是Go运行时中,协程并不能主动做什么事情来进行g0的切换,即使是使用信号机制也只能触发m0中g0的切换

所以在Golang中,需要有多种不类似的方式触发g态向g0态切换,比如

  1. 编译期向g态很多函数的一开始注入g栈空间检查的代码,通过这段代码进入g0态,然后触发调度
  2. 使用单独的线程sysmon,监控g连续运行时间,发现太长,就被动抢占以及发信号主动强占

Go中的基于信号的主动抢占,有点类似于用户态进程向内核发出软中断,但是信号只能被主线程接收到,只能完成主线程m0的g0态切换

内核中没有一个单独的核像sysmon一样用来监控,个人认为是没有必要,因为进入内核态的机会太多了,到处都有各种中断指令发过去(最不济也有定时器中断),内核不用担心自己没有机会调度线程。而且内核使用时钟中断做定期调度,类似于sysmon的功能

但Go中不一样,如果仅仅是依靠函数一开始morestack的检查以及sysmon,不引入基于信号的主动抢占,那么单核中碰到只有cpu密集操作的协程,运行时就得不到运行机会,也就是永远切换不到g0态

系统层面屏蔽线程,暴露协程

Golang从系统层面直接屏蔽了线程,而是将协程暴露给用户使用

有人认为这种做法太激进了,要是我在某些场景需要自己使用线程呢?

我认为不然,大可以将协程Goroutine当成线程使用,你如果需要独占线程,大可以使用runtime.LockOSThread独占

Golang的这种设计可以避免开发者对线程的滥用,比如大量使用线程处理任务,导致产生大量的线程,使得整体效率变慢

为什么会变慢?看看协程相对线程具有哪些优势

线程的切换除了上下文的保存以及恢复外,还有线程的优先级管理的复杂操作

而协程的上下文切换全部处于用户态,由用户态调度器负责调度,只存在上下文的保存以及恢复等(做的事情比线程的上下文切换少很多),所以协程的效率要比线程高

总结下协程的优势:

  1. 协程的调度比线程的调度成本更低,没有更多复杂操作,比如线程的优先级管理等等
  2. 协程的引入可以大量减少线程的数量,内核对线程之间的调度大量都变成了Go运行时对协程的调度

其他语言当然也可以实现协程,比如Java,完全可以写一个Go的运行时出来,但是想想,Java写出来的协程是运行在Java虚拟机之上的

而Go中协程是系统层级的,其中或多或少就会有一些性能差异

语法简单,门槛低

Golang中关键字非常少,相对其他语言,门槛非常低

内存占用低

云原生利器

等等




微信关注我,及时接收最新技术文章