March 19, 2021
Runtime: Golang同步原语Mutex源码分析
"在 sync 包里提供了最基本的同步原语,如互斥锁 Mutex。除 Once 和 WaitGroup 类型外,大部分是由低级库提供的,更高级别的同步最好是通过 channel 通讯来实现。\nMutex 类型的变量默认值是未加锁状态,在第一次使用后,此值将不得复制,这点切记!!!\n本文基于go version: 1.16.2\nMutex 锁实现了 Locker 接口。\n// A Locker represents an object that can be locked and unlocked. type Locker interface { Lock() Unlock() } 锁的模式 为了互斥公平性,Mutex 分为 正常模式 和 饥饿模式 两种。\n正常模式 在正常模式下,等待者 waiter 会进入到一个FIFO队列,在获取锁时waiter会按照先进先出的顺序获取。当唤醒一个waiter 时它被并不会立即获取锁,而是要与新来的goroutine竞争,这种情况下新来的goroutine比较有优势,主要是因为它已经运行在CPU,可能它的数量还不少,所以waiter大概率下获取不到锁。 …"
March 5, 2021
Golang什么时候会触发GC
"Golang采用了三色标记法来进行垃圾回收,那么在什么场景下会触发这个GC动作呢?\n源码主要位于文件 [src/runtime/mgc.go](https://github.com/golang/go/blob/go1.16/src/runtime/mgc.go) go version 1.16\n触发条件从大方面来说,分为 手动触发 和 系统触发 两种方式。手动触发一般很少用,主要通过开发者调用 runtime.GC() 函数来实现,而对于系统自动触发是 运行时 根据一些条件自行维护的,这也正是本文要介绍的内容。\n不管哪种触发方式,底层回收机制是一样的,所以我们先看一下手动触发,看看能否根据它来找GC触发所需的条件。\n// src/runtime/mgc.go // GC runs a garbage collection and blocks the caller until the // garbage collection is complete. It may also block the entire // program. func GC() { n := …"
March 4, 2021
Golang 基于信号的异步抢占与处理
"在Go1.14版本开始实现了 基于信号的协程抢占调度 模式,在此版本以前执行以下代码是永远也无法执行最后一条println语句。\n本文基于go version 1.16\npackage main import ( \u0026#34;runtime\u0026#34; \u0026#34;time\u0026#34; ) func main() { runtime.GOMAXPROCS(1) go func() { for { } }() time.Sleep(time.Millisecond) println(\u0026#34;OK\u0026#34;) } 原因很简单:在main函数里只有一个CPU,从上到下执行到 time.Sleep() 函数的时候,会将 main goroutine 放入运行队列,出让了P,开始执行匿名函数,但匿名函数是一个for循环,没有任何 IO 语句,也就无法引起对 G 的调度,所以当前仅有的一个 P 永远被其占用,导致无法打印OK。\n这个问题在1.14版本开始有所改变,主要是因为引入了基于信号的抢占模式。在程序启动时,初始化信号,并在 runtime.sighandler 函数注册了 SIGURG 信号的处理 …"
March 1, 2021
Golang 的调度策略之G的窃取
"我们上篇文章( Golang 的底层引导流程/启动顺序)介绍了一个golang程序的启动流程,在文章的最后对于最重要的一点“调度“ (函数 [schedule()](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L2607-L2723)) 并没有展开来讲,今天我们继续从源码来分析一下它的调度机制。\n在此之前我们要明白golang中的调度主要指的是什么?在 src/runtime/proc.go 文件里有一段注释这样写到\n// Goroutine scheduler\n// The scheduler’s job is to distribute ready-to-run goroutines over worker threads.\n这里指如何找一个已准备好运行的 G 关联到PM 让其执行。对于G 的调度可以围绕三个方面来理解:\n时机:什么时候关联(调度)。对于调度时机一般是指有空闲P的时候都会去找G执行 对象:选择哪个G进行调度。这是我们本篇要讲的内容 机制:如何调度。execute() 函数 理解了这三个 …"
February 27, 2021
Runtime: Golang是如何处理系统调用阻塞的?
"我们知道在Golang中,当一个Goroutine由于执行 系统调用 而阻塞时,会将M从GPM中分离出去,然后P再找一个G和M重新执行,避免浪费CPU资源,那么在内部又是如何实现的呢?今天我们还是通过学习Runtime源码的形式来看下他的内部实现细节有哪些?\ngo version 1.15.6\n我们知道一个P有四种运行状态,而当执行系统调用函数阻塞时,会从 _Prunning 状态切换到 _Psyscall,等系统调用函数执行完毕后再切换回来。P的状态切换\n从上图我们可以看出 P 执行系统调用时会执行 [entersyscall()](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L3134-L3142) 函数(另还有一个类似的阻塞函数 entersyscallblock() ,注意两者的区别)。当系统调用执行完毕切换回去会执行 exitsyscall() 函数,下面我们看一下这两个函数的实现。\n进入系统调用 // Standard syscall entry used by the go syscall …"
February 26, 2021
Runtime: 当一个goroutine 运行结束后会发生什么
"上一篇我们介绍了 创建一个goroutine 会经历些什么,今天我们再看下当一个goroutine 运行结束的时候,又会发生什么?\ngo version 1.15.6。\n主要源文件为 [src/runtime/proc.go](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go)。\n当一个goroutine 运行结束的时候,默认会执行一个 [goexit1()](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L2941-L2950) 的函数,这是一个只有八行代码的函数,其中最后以通过 [mcall()](https://github.com/golang/go/blob/go1.15.6/src/runtime/stubs.go#L34) 调用 [goexit0](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L2952-L3011) 函数结束。因此我们主 …"
February 17, 2021
Runtime: 创建一个goroutine都经历了什么?
"我们都知道goroutine的在golang中发挥了很大的作用,那么当我们创建一个新的goroutine时,它是怎么一步一步创建的呢?都经历了哪些操作呢?今天我们通过源码来剖析一下创建goroutine都经历了些什么?go version 1.15.6\n对goroutine最关键的两个函数是 [newproc()](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L3535-L3564) 和 [newproc1()](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L3566-L3674),而 newproc1() 函数是我们最需要关注的。\n函数 newproc() 我们先看一个简单的创建goroutine的例子,找出来创建它的函数。\npackage main func start(a, b, c int64) { _ = a + b + c } func main() { go start(7, 2, 5) } 输出结果:\n➜ …"
February 15, 2021
Runtime: 理解Golang中接口interface的底层实现
"接口类型是Golang中是一种非常非常常见的数据类型,每个开发人员都很有必要知道它到底是如何使用的,如果了解了它的底层实现就对开发就更有帮助了。\n接口的定义 在Golang中 interface 通常是指实现了一 组抽象方法的集合,它提供了一种无侵入式的方式。当你实现了一个接口中指定的所有方法的时候,那么就实现了这个接口,在Golang中对它的实现并不需要 implements 关键字。\n有时候我们称这种模型叫做鸭子模型(Duck typing),维基百科对鸭子模型的定义是\n”If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.“\n翻译过来就是 ”如果它看起来像鸭子,像鸭子一样游泳,像鸭子一样嘎嘎叫,那他就可以认为是鸭子“。\nGo 不同版本之间interface的结构可能不太一样,但整体都差不多,这里使用的Go版本为 1.15.6。\n数据结构 Go 中 interface 在运行时可分 eface 和 iface 两种数据结构,我们先看一下对它们的定 …"
February 13, 2021
认识Golang中的sysmon监控线程
"Go Runtime 在启动程序的时候,会创建一个独立的 M 作为监控线程,称为 sysmon,它是一个系统级的 daemon 线程。这个sysmon 独立于 GPM 之外,也就是说不需要P就可以运行,因此官方工具 go tool trace 是无法追踪分析到此线程( 源码)。sysmon\n在程序执行期间 sysmon 每隔 20us~10ms 轮询执行一次( 源码),监控那些长时间运行的 G 任务, 然后设置其可以被强占的标识符,这样别的 Goroutine 就可以抢先进来执行。\n// src/runtime/proc.go // forcegcperiod is the maximum time in nanoseconds between garbage // collections. If we go this long without a garbage collection, one // is forced to run. // // This is a variable for testing purposes. It normally doesn\u0026#39;t …"
February 11, 2021
g0 特殊的goroutine
"在上篇 《golang中G、P、M 和 sched 三者的数据结构》文章中,我们介绍了G、M 和 P 的数据结构,其中M结构体中第一个字段是 g0,这个字段也是一个 goroutine,但和普通的 goroutine 有一些区别,它主要用来实现对 goroutine 进行调度,下面我们将介绍它是如何实现调度goroutine的。\n另外还有一个 m0 , 它是一个全局变量,与 g0 的区别如下M0 与 g0的区别\n本文主要翻译自 Go: g0, Special Goroutine 一文,有兴趣的可以查阅原文,作者有一系列高质量的文章推荐大家都阅读一遍。ℹ️ 本文基于 Go 1.13。\n我们知道在Golang中所有的goroutine的运行都是由调度器来负责管理的,go调度器尝试为所有的goroutine来分配运行时间,当有goroutine被阻塞或终止时,调度器会通过对goroutine 进行调度以此来保证所有CPU都处于忙碌状态,避免有CPU空闲状态浪费时间。\ngoroutine 切换规则 在此之前我们需要记住一些goroutine切换规则。runtime源码\n// …"