DevilKing's blog

冷灯看剑,剑上几分功名?炉香无需计苍生,纵一穿烟逝,万丈云埋,孤阳还照古陵

0%

golang 机制了解

原文链接

go语言初始化过程

这里解释一下本地线程存储。比如说每个goroutine都有自己的控制信息,这些信息是存放在一个结构体G中。假设我们有一个全局变量g是结构体G的指针,我们希望只有唯一的全局变量g,而不是g0,g1,g2…但是我们又希望不同goroutine去访问这个全局变量g得到的并不是同一个东西,它们得到的是相对自己线程的结构体G,这种情况下就需要本地线程存储。g确实是一个全局变量,却在不同线程有多份不同的副本。每个goroutine去访问g时,都是对应到自己线程的这一份副本。针对goroutine部分,

1
2
3
4
5
6
7
8
9
10
CLD                // convention is D is always left cleared
CALL runtime·check(SB) //检测像int8,int16,float等是否是预期的大小,检测cas操作是否正常
MOVL 16(SP), AX // copy argc
MOVL AX, 0(SP)
MOVQ 24(SP), AX // copy argv
MOVQ AX, 8(SP)
CALL runtime·args(SB) //将argc,argv设置到static全局变量中了
CALL runtime·osinit(SB) //osinit做的事情就是设置runtime.ncpu,不同平台实现方式不一样
CALL runtime·hashinit(SB) //使用读/dev/urandom的方式从内核获得随机数种子
CALL runtime·schedinit(SB) //内存管理初始化,根据GOMAXPROCS设置使用的procs等等

go关键字的调用协议:先将参数进栈,再被调函数指针和参数字节数进栈,接着调用runtime.newproc函数。所以这里其实就是新开个goroutine执行runtime.main

1
2
3
找到一个等待运行的g
如果g是锁定到某个M的,则让那个M运行
否则,调用execute函数让g在当前的M中运行

goroutine状态图

1
2
3
4
5
6
7
8
9
10
11
12
13
func M() {
for {
sched.lock.Lock() //互斥地从就绪G队列中取一个g出来运行
if sched.allg > 0 {
g := sched.allg[0]
sched.allg = sched.allg[1:]
sched.lock.Unlock()
g.Run() //运行它
} else {
sched.lock.Unlock()
}
}
}

退出goroutine

1
2
3
4
5
6
7
8
func exitsyscall() {
if len(allm) >= GOMAXPROCS {
sched.lock.Lock()
sched.allg = append(sched.allg, g) //把g放回到队列中
sched.lock.Unlock()
time.Sleep() //这个M不再干活
}
}

内存管理

在多线程方面,很自然的做法就是每条线程都有自己的本地的内存,然后有一个全局的分配链,当某个线程中内存不足后就向全局分配链中申请内存。这样就避免了多线程同时访问共享变量时的加锁。 在避免内存碎片方面,大块内存直接按页为单位分配,小块内存会切成各种不同的固定大小的块,申请做任意字节内存时会向上取整到最接近的块,将整块分配给申请者以避免随意切割。

分配器的数据结构包括:

  • FixAlloc: 固定大小(128kB)的对象的空闲链分配器,被分配器用于管理存储
  • MHeap: 分配堆,按页的粒度进行管理(4kB),用于直接分配较大(>32kB)的内存空间
  • MSpan: 一些由MHeap管理的页
  • MCentral: 对于给定尺寸类别的共享的free list
  • MCache: 用于小对象的每M一个的cache

我们可以将Go语言的内存管理看成一个两级的内存管理结构,MHeap和MCache。

非阻塞io

底层非阻塞io是如何实现的呢?简单地说,所有文件描述符都被设置成非阻塞的,某个goroutine进行io操作,读或者写文件描述符,如果此刻io还没准备好,则这个goroutine会被放到系统的等待队列中,这个goroutine失去了运行权,但并不是真正的整个系统“阻塞”于系统调用。

后台还有一个poller会不停地进行poll,所有的文件描述符都被添加到了这个poller中的,当某个时刻一个文件描述符准备好了,poller就会唤醒之前因它而阻塞的goroutine,于是goroutine重新运行起来