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// …"
January 26, 2021
Golang环境变量之GODEBUG
"GODEBUG 是 golang中一个控制runtime调度变量的变量,其值为一个用逗号隔开的 name=val对列表,常见有以下几个命名变量。\nallocfreetrace 设置allocfreetrace = 1会导致对每个分配进行概要分析,并在每个对象的分配上打印堆栈跟踪并释放它们。\nclobberfree 设置 clobberfree=1会使垃圾回收器在释放对象的时候,对象里的内存内容可能是错误的。\ncgocheck cgo相关。\n设置 cgocheck=0 将禁用当包使用cgo非法传递给go指针到非go代码的检查。如果值为1(默认值)会启用检测,但可能会丢失有一些错误。如果设置为2的话,则不会丢失错误。但会使程序变慢。\nefence 设置 efence=1会使回收器运行在一个模式。每个对象都在一个唯一的页和地址,且永远也不会被回收。\ngccheckmark GC相关。\n设置 gccheckmark=1 启用验证垃圾回收器的并发标记,通过在STW时第二个标记阶段来实现,如果在第二阶段的时候,找到一个可达对象,但未找到并发标记,则GC会发生Panic。\ngcpacertrace …"
January 26, 2021
Golang中MemStats的介绍
"平时在开发中,有时间需要通过查看内存使用情况来分析程序的性能问题,经常会使用到 MemStats 这个结构体。但平时用到的都是一些最基本的方法,今天我们全面认识一下MemStas。\n相关文件为 src/runtime/mstats.go ,本文章里主要是与内存统计相关。\nMemStats 结构体 // MemStats记录有关内存分配器的统计信息 type MemStats struct { // General statistics. Alloc uint64 TotalAlloc uint64 Sys uint64 Lookups uint64 Mallocs uint64 Frees uint64 // Heap memory statistics. HeapAlloc uint64 HeapSys uint64 HeapIdle uint64 HeapInuse uint64 HeapReleased uint64 HeapObjects uint64 // Stack memory statistics. StackInuse uint64 StackSys uint64 …"
January 25, 2021
Golang中Stack的管理
"栈的演变 在 Go1.13之前的版本,Golang 栈管理是使用的分段栈(Segment Stacks)机制来实现的,由于sgement stack 存在 热分裂(hot split)的问题,后面版本改为采用连续栈( [Contiguous stacks](https://docs.google.com/document/d/1wAaf1rYoM4S4gtnPh0zOlGzWtrZFQ5suE8qr2sD8uWQ/pub))机制( 说明)。\n分段栈(Segment Stack) 分段栈是指开始时只有一个stack,当需要更多的 stack 时,就再去申请一个,然后将多个stack 之间用双向链接连接在一起。当使用完成后,再将无用的 stack 从链接中删除释放内存。segment stack\n可以看到这样确实实现了stack 按需增长和收缩,在增加新stack时不需要拷贝原来的数据,系统使用率挺高的。但在一定特别的情况下会存在 热分裂(hot split) 的问题。\n当一个 stack 即将用完的时候,任意一个函数都会导致堆栈的扩容,当函数执行完返回后,又要触发堆栈的收缩。如果这个操作 …"
January 22, 2021
Golang 的底层引导流程/启动顺序
"在Golang中,程序的执行入口为 main() 函数,那么底层又是如何工作的呢? 这个问题的答案我们可以在runtime源码找到。对它的解释主要在 [src/runtime/proc.go](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go) 文件,下面我们看一下它是如何一步一步开始执行的。go version 1.15.6\n在文件头部有一段对 [Goroutine scheduler](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L19) 的介绍,我们先了解一下。\n调度器的工作是分发goroutines到工作线程让其运行。一句话指明了调度器的存在意义,就是指挥协调GPM干活。\n主要包含三部分 G 指的是 goroutine M 工作线程,也叫machine P 处理器(逻辑CPU),执行 Go code 的一种资源。这里的Go code 其实就是 goroutine里的代码。\nM必须被指派给P去执行 Go code, 但可以被 …"
January 21, 2021
golang中G、P、M 和 sched 三者的数据结构
"G、P、M 三者是golang实现高并发能的最为重要的概念,runtime 通过 调度器 来实现三者的相互调度执行,通过 p 将用户态的 g 与内核态资源 m 的动态绑定来执行,以减少以前通过频繁创建内核态线程而产生的一系列的性能问题,充分发挥服务器最大有限资源。GPM 协作\n调度器的工作是将一个 G(需要执行的代码)、一个 M(代码执行的地方)和一个 P(代码执行所需要的权限和资源)结合起来。\n所有的 g、m 和 p 对象都是分配在堆上且永不释放的,所以它们的内存使用是很稳定的。得益于此,runtime 可以在调度器实现中避免写屏障。当一个G执行完成后,可以放入pool中被再次使用,避免重复申请资源。\n本节主要通过阅读runtime源码来认识这三个组件到底长的是什么样子,以此加深对 GPM 的理解。go version go1.15.6\n理解下文前建议先阅读一下 src/runtime/HACKING.md 文件,中文可阅读 这里,这个文件内容是面向开发者理解runtime的很值得看一看。\n本文若没有指定源码文件路径,则默认为 src/runtime/runtime2.go。\nG G …"
January 18, 2021
Runtime: Golang中channel实现原理源码分析
"channel是golang中特有的一种数据结构,通常与goroutine一起使用,下面我们就介绍一下这种数据结构。\nchannel数据结构 channel 是Golang 中最重要的一个数据结构,源码里对应的结构体是hchan,当我们创建一个channel 的时候,实际上是创建了一个hchan结构体。\nhchan结构体 // src/runtime/chan.go type hchan struct { qcount uint // total data in the queue dataqsiz uint // size of the circular queue buf unsafe.Pointer // points to an array of dataqsiz elements elemsize uint16 closed uint32 elemtype *_type // element type sendx uint // send index recvx uint // receive index recvq waitq // list of recv waiters …"