March 29, 2021
Runtime: Golang 定时器实现原理及源码解析
"定时器作为开发经常使用的一种数据类型,是每个开发者需要掌握的,对于一个高级开发很有必要了解它的实现原理,今天我们runtime源码来学习一下它的底层实现。\n定时器分两种,分别为 Timer 和 Ticker,两者差不多,这里重点以Timer为例。\n源文件位于 [src/time/sleep.go](https://github.com/golang/go/blob/go1.16.2/src/time/sleep.go) 和 [src/time/tick.go](https://github.com/golang/go/blob/go1.16.2/src/time/tick.go) 。 go version 1.16.2\n数据结构 Timer 数据结构\n// src/runtime/sleep.go // The Timer type represents a single event. // When the Timer expires, the current time will be sent on C, // unless the Timer was created by …"
March 28, 2021
Golang中的CAS原子操作 和 锁
"在高并发编程中,经常会出现对同一个资源并发访问修改的情况,为了保证最终结果的正确性,一般会使用 锁 和 CAS原子操作 来实现。\n如要对一个变量进行计数统计,两种实现方式分别为\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; ) // 锁实现方式 func main() { var count int64 var wg sync.WaitGroup var mu sync.Mutex for i := 0; i \u0026lt; 10000; i++ { wg.Add(1) go func(wg *sync.WaitGroup) { defer wg.Done() mu.Lock() count = count + 1 mu.Unlock() }(\u0026amp;wg) } wg.Wait() // count = 10000 fmt.Println(\u0026#34;count = \u0026#34;, count) } 与\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; …"
March 23, 2021
Golang并发同步原语之-信号量Semaphore
"信号量是并发编程中比较常见的一种同步机制,它会保持资源计数器一直在0-N(N表示权重值大小,在用户初始化时指定)之间。当用户获取的时候会减少一点,使用完毕后再恢复过来。当遇到请求时资源不够的情况下,将会进入休眠状态以等待其它进程释放资源。\n在 Golang 官方扩展库中为我们提供了一个基于权重的信号量 [semaphore](https://github.com/golang/sync/blob/master/semaphore/semaphore.go) 并发原语。\n你可以将下面的参数 n 理解为资源权重总和,表示每次获取时的权重;也可以理解为资源数量,表示每次获取时必须一次性获取的资源数量。为了理解方便,这里直接将其理解为资源数量。\n数据结构 [semaphoreWeighted](https://github.com/golang/sync/blob/master/semaphore/semaphore.go#L19-L33) 结构体\ntype waiter struct { n int64 ready chan\u0026lt;- struct{} // Closed when …"
March 22, 2021
学习Golang GC 必知的几个知识点
"对于gc的介绍主要位于 [src/runtime/mgc.go](https://github.com/golang/go/blob/go1.16.2/src/runtime/mgc.go),以下内容是对注释的翻译。\nGC 四个阶段 通过源文件注释得知GC共分四个阶段:\nGC 清理终止 (GC performs sweep termination) a. Stop the world, 每个P 进入GC safepoint(安全点),从此刻开始,万物静止。 b. 清理未被清理的span,如果GC被强制执行时才会出现这些未清理的span GC 标记阶段(GC performs the mark phase) a. 将gc标记从 _GCoff 修改为 _GCmark,开启写屏障(write barries)和 协助助手(mutator assists),将根对象放入队列。 在STW期间,在所有P都启用写屏障之前不会有什么对象被扫描。 b. Start the world(恢复STW)。标记工作线程和协助助手并发的执行。对于任何指针的写操作和指针值,都会被写屏障覆盖,使新分配的对象标记为黑 …"
March 20, 2021
Runtime: Golang 之 sync.Pool 源码分析
"Pool 指一组可以单独保存和恢复的 临时对象。Pool 中的对象随时都有可能在没有收到任何通知的情况下被GC自动销毁移除。\n多个goroutine同时操作Pool是并发安全的。\n源文件为 [src/sync/pool.go](https://github.com/golang/go/blob/master/src/sync/pool.go) go version: 1.16.2\n为什么使用Pool 在开发高性能应用时,经常会有一些完全相同的对象需要频繁的创建和销毁,每次创建都需要在堆中分配对象,等使用完毕后,这些对象需要等待GC回收。我们知道在Golang中使用三色标记法进行垃圾回收的,在回收期间会有一个短暂STW(stop the world)的时间段,这样就会导致程序性能下降。\n那么能否实现类似数据库连接池这种效果,用来避免对象的频繁创建和销毁,达到尽可能的资源复用呢?为了实现这种需求,标准库中有了sync.Pool 这个数据结构。看名字很知道它是一个池。但是它和我们想象中的数据库连接池还是有些差别的。对于数据库连接池这种资源只要不手动释放就可以一直利用, …"
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 …"