Go合集
·
giftia
协程 线程 进程的区别
- 进程:资源分配的基本单位,比如启动一个程序
- 线程:cpu 调度的基本单位,内核态
- 协程:用户态的轻量级线程
make 和 new 的区别
- new 初始化任意数据类型并返回指针
- make 初始化 slice/channel/map,返回可用的数据结构本身
slice 底层
- 指向底层数组,有长度和容量
- 扩容:cap<256 翻倍,cap>256 逐步增长到约 1.25 倍
map 底层
- 2^B 个桶,每个桶 8 组 tophash、key、value
- 哈希冲突解决使用溢出桶
- 装载因子>6.5 或溢出桶过多触发扩容,分等量扩容和翻倍扩容,方式是渐进式哈希
channel 底层
- 环形队列,分有缓存和无缓冲,有等待发送和等待接收队列
- 并发安全
interface 底层
- iface:带方法的接口
- eface:空接口
互斥锁的实现
type Mutex struct {
state int32 // 状态位
sema uint32 // 信号量
}
- 原子操作加减 state
- sema 用于阻塞/唤醒协程
锁的两种状态
- 普通:新来的竞争者通常会和队头抢锁,新的正在占用 cpu,所以一般都能抢到
- 饥饿:新来的也必须在队尾排队
- 某个等待者超过 1ms 没拿到锁就进入饥饿模式;队头等待时间小于 1ms 就进入普通模式(或队列为空)
sync.map
- 适用读多写少
- 空间换时间,读写分离
- 读:先读 read map,没有就加锁读 dirty map
- 写:key 已存在就直接写 read map,新 key 就加锁写 dirty map
- 同步:read map 未命中次数 >= len(dirty) 时,dirty 升级为 read,然后等下一次写入时再 copy 一个 dirty
线程池(非go)
- 最大线程数
- 核心线程数:默认懒创建、默认常驻不回收
- 空闲存活时间:非核心线程空闲超时被回收,回收下限为核心线程数
- 工作队列
- 线程工厂
- 拒绝策略:报错/静默丢弃/调用者线程运行/丢弃队列最老任务
协程池
协程池的核心目的不是“复用资源”,而是“限制不可控的并发量”
- worker 数量:控制同时执行任务的最大协程数
- channel 缓冲大小:控制最多允许多少任务排队等待
panic 的捕获
- 同一协程
- recover 必须在 defer func 函数内
- 不能捕获
func main() {
defer recover()
panic("")
}
- 能捕获
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
}
}()
panic("")
}
defer 时机
func main() {
defer fmt.Println("A")
run()
defer fmt.Println("C")
fmt.Println("D")
}
func run() {
defer fmt.Println("B")
}
正确输出:BDCA(defer只在当前函数作用域内)
goroutine channel
三个 goroutine,一只能打印 A,二只能打印 B,三只能打印 C,如此循环
package main
import "fmt"
func main() {
chA := make(chan struct{})
chB := make(chan struct{})
chC := make(chan struct{})
go func() {
for {
<-chA
fmt.Print("A")
chB <- struct{}{}
}
}()
go func() {
for {
<-chB
fmt.Print("B")
chC <- struct{}{}
}
}()
go func() {
for {
<-chC
fmt.Print("C")
chA <- struct{}{}
}
}()
chA <- struct{}{} // 启动
select {} // 阻塞,保持程序运行
}