Go中的协程(三) | 青训营笔记

85 阅读2分钟

协程开太多会怎么样

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)

Pasted image 20230521151714.png * 预创建一定数量的协程 * 将任务送入协程池队列 * 协程池不断取出可用协程, 执行任务 * Go语言的线程, 已经相当于池化了 * 二级池化会增加系统复杂度 * Go语言的初衷是希望协程即用即毁, 不要池化

  • 调整系统资源

总结

  • 太多的协程会给程序运行带来性能和稳定性问题
  • 牺牲并发特性,利用channel缓冲

总结

为什么用协程

  • 协程用来精细利用线程
  • 协程可以支撑超高并发

协程是什么

  • 从runtime的角度看,协程是一个可以被调度的g结构体
  • 从线程的角度看,协程是一段程序,自带执行现场

GMP模型

  • 通过P结构体,达成了缓存部分G的目的
  • P本质上是一个G的本地队列,避免全局并发等待
  • 窃取式工作分配机制能够更加充分利用线程资源

协程并发

  • 如果协程顺序执行,会有饥饿问题
  • 协程执行中间,将协程挂起,执行其他协程
  • 完成系统调用时挂起,也可以主动挂起
  • 防止全局队列饥饿,本地队列随机抽取全局队列

抢占式调度

  • 基于系统调用和主动挂起,协程可能无法调度
  • 基于协作的抢占式调度:业务主动调用morestack()
  • 基于信号的抢占式调度:强制线程调用doSigPreempt()