1. Goroutine

1.1 进程, 线程和协程和 goroutine

  • 进程,线程: 内核态调度,抢占式 CPU 调度,
  • 协程: 用户态调度, 协作式 CPU 调度,需要协程主动让出 CPU 控制权.

goroutine 本质上就是协程, 不过 golang 对 goroutine 调度进行了封装和处理,当遇到长时间或者进行系统调用时,会主动让出当前 goroutine 的 CPU 控制权,让其他 goroutine 被调度.

总结:

goroutine 线程
栈大小 2KB 8MB
调度 用户态调度 内核态调度
CPU 协作式 抢占式

1.2 实现原理

一般情况网络服务器会有大量的连接需要处理,且总是大量的 I/O + 少量的计算,一个连接建立一个线程会消耗大量系统资源(1. 每个线程栈大小为 8MB, 2.频繁的 I/O 会阻塞当前线程使别的线程被调度,线程切换会带来大量的开销),所以这样不可行.而使用基于事件模式的异步编程模型(select, poll, epoll)可以使用少量的线程来服务大量的网络连接和 I/O 操作,但是需要开发人员手动管理每个连接的上下文,大幅度提高程序的复杂度.

goroutine是在应用层实现的类似于线程的编程模型,程序员可以像编写多线程代码一样编写协程代码,但协程运行时却和异步编程模型相同.
golang 对各种 I/O函数进行了封装,其内部调用了操作系统的异步 I/O 函数,当这些异步函数返回 busy 或者 blocking 时,golang 就将现有的执行序列保存,让程序去拉另外一个goroutine(就绪的)的代码来执行,当 I/O就绪时(基于epoll,select 等)继续在重新调度刚才的goroutine.

1.3 goroutine 调度

  • 协作式调度即非抢占式,程序的切换方式取决于程序本身
  • 抢占式调度: 程序的切换由操作系统(processor)控制

Go1.14 之前 Golang 为协作式的,在一下情况会进行 goroutine 切换

sysmonsysmonpreempttruetruefoo {}

1.3.1 异步抢占

sysmonsysmonSIGURGgsignalgoroutineGODEBUG=asyncpreemptoff=1

全局队列是与“本地队列”不同的队列,本地队列是存储 P 具有的 G。全局队列有以下几个作用。

存储那些超过本地队列容量(256)的 G
存储由于各种原因而等待的 G
存储由抢占标志分离的 G

1.4 GPM

  • G goroutine go 协程
  • P processor 执行器
  • M machine 工作线程

1.4.1 M 工作线程

主线程 M0

运行 runtime.main, 单个实例,

sysmon 线程

创建了新的 m, 这个 m 不需要绑定 p 就可以执行. 与整个调度系统脱离.

  • checkdead, 检查是否所有 goroutine 都已经 锁死, 如果锁死直接调用 runtime.throw,强制退出. 这个操作只在启动的时候做一次
  • 将 netpoll 返回的结果注入到全局 sched 的任务队列
  • 收回因为 syscall 而长时间阻塞的 p, 同时抢占哪些执行时间过长的 g
  • 如果 span 内存闲置超过 5min,那么释放掉

普通线程

就是 GPM 模型里的 M, M 对应的就是操作系统的线程.

M 会从绑定的 p 的本地队列、sched 中的全局队列、netpoll 中获取可运行的 g,实在找不着还会去其它的 p 那里去偷。

2. map

golang 的 map 是 hashmap(c++ stl 是红黑树)

  • 拉链法解决冲突,每个 bucket 可以存储 8 对 key/value
  • 会预先分配一些 bucket 用于溢出
  • 使用高位 hash 与运算来选择桶
  • 使用渐进式 hash 扩容hash 表
  • count/(2B) > 6.5 即翻倍扩容
  • 当 overflow >= 2^B 是触发等量扩容
3. 内存分配

3.1 内存分配核心思想

  • 每次从操作系统申请一大块内存(并不实际分配)
  • 采用 TMalloc 算法, 将内存对象切分为多级,以降低锁粒度
  • 回收对象内存时,并没有将其真正释放掉,只是放回预先分配的大块内存中,以便复用.

3.2 内存结构

spans bitmap arena
512MB 16GB 512GB
存放spans指针,每个 spans 指向page 保存 arena 对应的某个地址是否存在对象,以及对象是否被 gc 扫描过 有 8kb 的 page 组成

3.2.1 arena

arena 即我们通常意义上的heap, 由连续的 page 组成, 多个 page 组成spans,spans 用于具体分配内存,对象分配时会选择大小相近的 span,即一种大小的 span 只给一种确定大小的对象分配,这样来减少内存碎片的产生.

3.2.2 bitmap

bitmap 即位图,一个 byte 标记 4 个对象,分别标记对应地址是否有存在对象和此对象是否被 gc 标记过.

3.2.3

参考资料
https://xargin.com/go-scheduler/https://blog.csdn.net/lsj1342/article/details/119797966