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. mapgolang 的 map 是 hashmap(c++ stl 是红黑树)
- 拉链法解决冲突,每个 bucket 可以存储 8 对 key/value
- 会预先分配一些 bucket 用于溢出
- 使用高位 hash 与运算来选择桶
- 使用渐进式 hash 扩容hash 表
- count/(2B) > 6.5 即翻倍扩容
- 当 overflow >= 2^B 是触发等量扩容
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