Golang环境变量之GODEBUG

GODEBUG 是 golang中一个控制runtime调度变量的变量,其值为一个用逗号隔开的 name=val对列表,常见有以下几个命名变量。

allocfreetrace

设置allocfreetrace = 1会导致对每个分配进行概要分析,并在每个对象的分配上打印堆栈跟踪并释放它们。

clobberfree

设置 clobberfree=1会使垃圾回收器在释放对象的时候,对象里的内存内容可能是错误的。

cgocheck

cgo相关。

设置 cgocheck=0 将禁用当包使用cgo非法传递给go指针到非go代码的检查。如果值为1(默认值)会启用检测,但可能会丢失有一些错误。如果设置为2的话,则不会丢失错误。但会使程序变慢。

Continue reading

Golang中MemStats的介绍

平时在开发中,有时间需要通过查看内存使用情况来分析程序的性能问题,经常会使用到 MemStats 这个结构体。但平时用到的都是一些最基本的方法,今天我们全面认识一下MemStas。

相关文件为 src/runtime/mstats.go ,本文章里主要是与内存统计相关。

MemStats 结构体

// 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

	// Off-heap memory statistics.
	MSpanInuse uint64
	MSpanSys uint64
	MCacheInuse uint64
	MCacheSys uint64
	BuckHashSys uint64
	GCSys uint64
	OtherSys uint64

	// Garbage collector statistics.
	NextGC uint64
	LastGC uint64
	PauseTotalNs uint64
	PauseNs [256]uint64
	PauseEnd [256]uint64
	NumGC uint32
	NumForcedGC uint32
	GCCPUFraction float64
	EnableGC bool
	DebugGC bool

	// BySize reports per-size class allocation statistics.
	BySize [61]struct {
		Size uint32
		Mallocs uint64
		Frees uint64
	}

}

可以清楚的看到,统计信息共分了五类

  • 常规统计信息(General statistics)
  • 分配堆内存统计(Heap memory statistics)
  • 栈内存统计(Stack memory statistics)
  • 堆外内存统计信息(Off-heap memory statistics)
  • 垃圾回收器统计信息(Garbage collector statistics)
  • 按 per-size class 大小分配统计(BySize reports per-size class allocation statistics)

以下按分类对每一个字段进行一些说明,尽量对每一个字段的用处可以联想到日常我们工作中用到的一些方法。

Continue reading

Golang中Stack的管理

栈的演变

在 Go1.13之前的版本,Golang 栈管理是使用的分段栈(Segment Stacks)机制来实现的,由于sgement stack 存在 热分裂(hot split)的问题,后面版本改为采用连续栈(Contiguous stacks机制(说明)。

分段栈(Segment Stack)

分段栈是指开始时只有一个stack,当需要更多的 stack 时,就再去申请一个,然后将多个stack 之间用双向链接连接在一起。当使用完成后,再将无用的 stack 从链接中删除释放内存。

segment stack

可以看到这样确实实现了stack 按需增长和收缩,在增加新stack时不需要拷贝原来的数据,系统使用率挺高的。但在一定特别的情况下会存在 热分裂(hot split) 的问题。

当一个 stack 即将用完的时候,任意一个函数都会导致堆栈的扩容,当函数执行完返回后,又要触发堆栈的收缩。如果这个操作是在一个for语句里执行的话,则过多的malloc 和 free 则会导致系统资源开销非常的大。

Continue reading

Golang 的底层引导流程/启动顺序

在Golang中,程序的执行入口为 main() 函数,那么底层又是如何工作的呢? 这个问题的答案我们可以在runtime源码找到。对它的解释主要在 src/runtime/proc.go 文件,下面我们看一下它是如何一步一步开始执行的。go version 1.15.6

在文件头部有一段对 Goroutine scheduler 的介绍,我们先了解一下。

调度器的工作是分发goroutines到工作线程让其运行。一句话指明了调度器的存在意义,就是指挥协调GPM干活。

Continue reading

golang中G、P、M 和 sched 三者的数据结构

G、P、M 三者是golang实现高并发能的最为重要的概念,runtime 通过 调度器 来实现三者的相互调度执行,通过 p 将用户态的 g 与内核态资源 m 的动态绑定来执行,以减少以前通过频繁创建内核态线程而产生的一系列的性能问题,充分发挥服务器最大有限资源。

GPM 协作

调度器的工作是将一个 G(需要执行的代码)、一个 M(代码执行的地方)和一个 P(代码执行所需要的权限和资源)结合起来。

所有的 g、m 和 p 对象都是分配在上且永不释放的,所以它们的内存使用是很稳定的。得益于此,runtime 可以在调度器实现中避免写屏障。当一个G执行完成后,可以放入pool中被再次使用,避免重复申请资源。

本节主要通过阅读runtime源码来认识这三个组件到底长的是什么样子,以此加深对 GPM 的理解。go version go1.15.6

理解下文前建议先阅读一下 src/runtime/HACKING.md 文件,中文可阅读这里,这个文件内容是面向开发者理解runtime的很值得看一看。

本文若没有指定源码文件路径,则默认为 src/runtime/runtime2.go

G

G是英文字母goroutine的缩写,一般称为“协程”,其实这个词还是无法完整表达它的意思的,但这用的人的多了就成了统称。注意它与线程和进程的区别,这个应该很容易理解,每个gopher应该都知道。

每个 Goroutine 对应一个 g 结构体,它有自己的栈内存, G 存储 Goroutine 的运行堆栈、状态以及任务函数,可重复用,

Goroutine数据结构位于 src/runtime/runtime2.go 文件,注意此文件里有太多重要的底层数据结构,对于我们理解底层runtime非常的重要,建议大量多看看。不需要记住每一个数据结构,但需要的时候要能第一时间想到在哪里查找。

当一个 goroutine 退出时,g 对象会被放到一个空闲的 g 对象池中以用于后续的 goroutine 的使用, 以减少内存分配开销。

Continue reading

Runtime: Golang中channel实现原理源码分析

channel是golang中特有的一种数据结构,通常与goroutine一起使用,下面我们就介绍一下这种数据结构。

channel数据结构

channel 是Golang 中最重要的一个结构结构,源码里对应的结构体是hchan,当我们创建一个channel 的时候,实际上是创建了一个hchan结构体。

hchan结构体

// 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
	sendq    waitq  // list of send waiters

	// lock protects all fields in hchan, as well as several
	// fields in sudogs blocked on this channel.
	//
	// Do not change another G's status while holding this lock
	// (in particular, do not ready a G), as this can deadlock
	// with stack shrinking.
	lock mutex
}

字段说明

  • qcount 当前channel中的元素数量
  • dataqsiz 环形队列的大小
  • buf 指向dataqsize的数组指针,只有缓冲chan有效
  • closed 当前channel关闭状态
  • elemsize 存储元素的大小
  • elemtype 存储元素的数据类型
  • sendx 发送操作处理到的索引位置,最大值为数组buf的最大下标值
  • recvx 接收操作处理到的索引位置,最大值为数组buf的最大下标值
  • recvq 接收队列,双向链表,阻塞元素
  • sendq 发送列队,双向链表,阻塞元素
  • lock 锁,,用来保护sudog里的所的字段
hchan struct

其中elemsizeelemtype 表示存储数据的大小和类型;sendxrecvx是指向底层数据的索引位置,表示当前处理的进度位置;recvqsendq 是一个由双向链表实现的队列,它存储的内容是由于队列dataqsize过小,而阻塞的数据。

Continue reading

Runtime:源码解析Golang 的map实现原理

go version 1.15.6

map作为一种常见的 key-value 数据结构,不同语言的实现原理基本差不多。首先在系统里分配一段连接的内存地址作为数组,然后通过对map键进行hash算法(最终将键转换成了一个整型数字)定位到不同的桶bucket(数组的索引位置),然后将值存储到对应的bucket里

map hash算法

理想的情况下是一个bucket存储一个值,即数组的形式,时间复杂度为O(1)。

如果存在键值碰撞的话,可以通过 链表法 或者 开放寻址法 来解决。

Continue reading