协程开太多会怎么样
panic: too many concurrent operations on a single file or socket (max 1048575)
- 文件打开数限制
- 内存限制
- 调度开销过大
处理协程太多的方案
- 优化业务逻辑
- 利用channel缓冲区(限制总数)
func do(i int, ch chan struct{}) {
fmt.Println(i)
time.Sleep(time.Second)
<- ch
}
func main() {
c := make(chan struct{}, 3000)
for i := 0; i < math.MaxInt32; i++ {
c <- struct{}{}
go do(i, c)
}
time.Sleep(time.Hour)
}
- 协程池(tunny)
* 预创建一定数量的协程
* 将任务送入协程池队列
* 协程池不断取出可用协程, 执行任务
* Go语言的线程, 已经相当于池化了
* 二级池化会增加系统复杂度
* Go语言的初衷是希望协程即用即毁, 不要池化
- 调整系统资源
总结
- 太多的协程会给程序运行带来性能和稳定性问题
- 牺牲并发特性,利用channel缓冲
总结
为什么用协程
- 协程用来精细利用线程
- 协程可以支撑超高并发
协程是什么
- 从runtime的角度看,协程是一个可以被调度的g结构体
- 从线程的角度看,协程是一段程序,自带执行现场
GMP模型
- 通过P结构体,达成了缓存部分G的目的
- P本质上是一个G的本地队列,避免全局并发等待
- 窃取式工作分配机制能够更加充分利用线程资源
协程并发
- 如果协程顺序执行,会有饥饿问题
- 协程执行中间,将协程挂起,执行其他协程
- 完成系统调用时挂起,也可以主动挂起
- 防止全局队列饥饿,本地队列随机抽取全局队列
抢占式调度
- 基于系统调用和主动挂起,协程可能无法调度
- 基于协作的抢占式调度:业务主动调用morestack()
- 基于信号的抢占式调度:强制线程调用doSigPreempt()