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 …"
January 11, 2021
Runtime:源码解析Golang 的map实现原理
"go version 1.15.6\nmap作为一种常见的 key-value 数据结构,不同语言的实现原理基本差不多。首先在系统里分配一段连接的内存地址作为数组,然后通过对map键进行hash算法(最终将键转换成了一个整型数字)定位到不同的桶bucket(数组的索引位置),然后将值存储到对应的bucket里\n理想的情况下是一个bucket存储一个值,即数组的形式,时间复杂度为O(1)。\n如果存在键值碰撞的话,可以通过 链表法 或者 开放寻址法 来解决。\n链表法\n开放寻址法\n对于开放寻址法有多种算法,常见的有线性探测法,线性补偿探测法,随机探测法等,这里不再介绍。\nmap基本数据结构 hmap结构体 map的核心数据结构定义在 /runtime/map.go\n// A header for a Go map. type hmap struct { // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go. // Make sure this stays in sync …"
December 26, 2020
Golang并发模式之扇入FAN-IN和扇出FAN-OUT
"在现实世界中,经常有一些工作是属于流水线类型的,它们的每一个步骤都是紧密关联的,第一步先做什么,再做什么,最后做什么。特别是制造业这个行业,基本全是流水线生产车间。在我们开发中也经常遇到这类的业务场景。\n假如我们有个流水线共分三个步骤,分别是 job1、job2和job3。代码: https://play.golang.org/p/e7ZlP9ofXB3\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func job1(count int) \u0026lt;-chan int { outCh := make(chan int, 2) go func() { defer close(outCh) for i := 0; i \u0026lt; count; i++ { time.Sleep(time.Second) fmt.Println(\u0026#34;job1 finish:\u0026#34;, 1) outCh \u0026lt;- 1 } }() return outCh } func job2(inCh \u0026lt;-chan int) \u0026lt;-chan int …"
December 24, 2020
重新认识Golang中的空结构体
"认识空结构体 低层实现原理 空结构体之内存对齐 应用场景 在golang中,如果我们想实现一个set集合的话,一般会使用map来实现,其中将set的值作为map的键,对于map的值一般使用一个空结构体来实现,当然对map值也可以使用一个bool类型或者数字类型等,只要符合一个键值对应关系即可。但我们一般推荐使用struct{}来实现,为什么呢?\npackage main import \u0026#34;fmt\u0026#34; func main() { m := make(map[int]struct{}) m[1] = struct{}{} m[2] = struct{}{} if _, ok := m[1]; ok { fmt.Println(\u0026#34;exists\u0026#34;) } } 上面这段代码是一个很简单的使用map实现的set功能,这里是采用空结构体struct{}来实现。\n在分析为什么使用struct{}以前,我看先认识一个struct。\n认识空结构体 struct 我们先看一个这段代码\npackage main import ( \u0026#34;fmt\u0026#34; …"
December 17, 2020
Golang中的内存重排(Memory Reordering)
"什么是内存重排 内存重排指的是内存的读/写指令重排。\n为什么要内存重排 为了提升程序执行效率,减少一些IO操作,一些硬件或者编译器会对程序进行一些指令优化,优化后的结果可能会导致程序编码时的顺序与代码编译后的先后顺序不一致。\n就拿做饭场景来说吧,是先蒸米还是先炒菜,这两者是没有冲突的,编译器在编译时有可能与你要求的顺序不一样。\n编译器重排 如下面这段代码\nX = 0 for i in range(100): X = 1 print X 要实现打印100次1,很显示在for里面每次都执行X=1语句有些浪费资源,如果将初始变量值修改为1,是不是要快的多。编译器也分析到了这一点,于是在编译时对代码做了以下优化\nX = 1 for i in range(100): print X 最终输出结果是一样的,两段代码功能也一样。\n但是如果此时有另一个线程里执行了一个 X=0 的赋值语句的话(两个线程同时运行),那么输出结果就可能与我们想要的不一样了。\n优化前情况:第一个线程执行到了第3次print X 后,第二个线程执行了X=0,把X 的值进行了修改,结果就有可能是1110 1111(在线程执行第5 …"