导图社区 goroutine
goroutine思维导图,本文整理了 1、底层实现原理? 2、goroutine和线程的区别? 3、goroutine泄漏的场景? 4、如何查看正在执行的goroutine数量? 5、如何控制并发的goroutine数量? 6、什么时候抢占P? 7、关于启动流程? 8、goroutine挂起? 相关知识可以导出使用。
编辑于2023-02-07 11:21:41 广东goroutine思维导图,本文整理了 1、底层实现原理? 2、goroutine和线程的区别? 3、goroutine泄漏的场景? 4、如何查看正在执行的goroutine数量? 5、如何控制并发的goroutine数量? 6、什么时候抢占P? 7、关于启动流程? 8、goroutine挂起? 相关知识可以导出使用。
Mutex主要整理了1、互斥锁的实现原理;2、正常模式和饥饿模式的区别;3、互斥锁允许自旋的条件;4、读写锁的实现原理;5、可重入锁如何实现;6、原子操作有哪些?7、原子操作和锁的区别 七个方面。
MySQL面试题:聚簇索引:数据和索引存储到一起,找到索引就获取到了数据。聚簇索引是唯一的,InnoDB一定会有一个聚簇索引来保存数据。非聚簇索引一定存储有聚簇索引的列值。
社区模板帮助中心,点此进入>>
goroutine思维导图,本文整理了 1、底层实现原理? 2、goroutine和线程的区别? 3、goroutine泄漏的场景? 4、如何查看正在执行的goroutine数量? 5、如何控制并发的goroutine数量? 6、什么时候抢占P? 7、关于启动流程? 8、goroutine挂起? 相关知识可以导出使用。
Mutex主要整理了1、互斥锁的实现原理;2、正常模式和饥饿模式的区别;3、互斥锁允许自旋的条件;4、读写锁的实现原理;5、可重入锁如何实现;6、原子操作有哪些?7、原子操作和锁的区别 七个方面。
MySQL面试题:聚簇索引:数据和索引存储到一起,找到索引就获取到了数据。聚簇索引是唯一的,InnoDB一定会有一个聚簇索引来保存数据。非聚簇索引一定存储有聚簇索引的列值。
goroutine
1、底层实现原理
概念
Goroutine可以理解为一种Go语言的协程(轻量级线程),是Go支持高并发的基础,属于用户态的线程,由Go runtime管理而不是操作系统。
底层数据结构
type g struct { goid int64 // 唯一的goroutine的ID sched gobuf // goroutine切换时,用于保存g的上下文 stack stack // 栈 gopc // pc of go statement that created this goroutine startpc uintptr // pc of goroutine function ... } type gobuf struct { sp uintptr // 栈指针位置 pc uintptr // 运行到的程序位置 g guintptr // 指向 goroutine ret uintptr // 保存系统调用的返回值 ... } type stack struct { lo uintptr // 栈的下界内存地址 hi uintptr // 栈的上界内存地址 }
阻塞
channel的读写操作、等待锁、等待网络数据、系统调用等都有可能发生阻塞,会调用底层函数runtime.gopark(),会让出CPU时间片,让调度器安排其它等待的任务运行,并在下次某个时候从该位置恢复执行。 当调用该函数之后,goroutine会被设置成waiting状态
唤醒
处于waiting状态的goroutine,在调用runtime.goready()函数之后会被唤醒,唤醒的goroutine会被重新放到M对应的上下文P对应的runqueue中,等待被调度。 当调用该函数之后,goroutine会被设置成runnable状态
退出
当goroutine执行完成后,会调用底层函数runtime.Goexit() 当调用该函数之后,goroutine会被设置成dead状态
2、goroutine和线程的区别
协程
是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
线程
是进程的一个实体,线程是内核态,而且是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
区别
内存占用
创建一个 goroutine 的栈内存消耗为 2 KB,实际运行过程中,如果栈空间不够用,会自动进行扩容
创建一个 线程 的栈内存消耗为 1 MB
创建和销毁
goroutine 因为是由 Go runtime 负责管理的,创建和销毁的消耗非常小,是用户级。
线程 创建和销毀都会有巨大的消耗,因为要和操作系统打交道,是内核级的,通常解决的办法就是线程池
切换
goroutines 切换只需保存三个寄存器:PC、SP、BP goroutine 的切换约为 200 ns,相当于 2400-3600 条指令。
当线程切换时,需要保存各种寄存器,以便恢复现场。 线程切换会消耗 1000-1500 ns,相当于 12000-18000 条指令。
进程
进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。
3、goroutine泄漏的场景
泄漏原因
Goroutine 内进行channel/mutex 等读写操作被一直阻塞。
Goroutine 内的业务逻辑进入死循环,资源一直无法释放。
Goroutine 内的业务逻辑进入长时间等待,有不断新增的 Goroutine 进入等待
泄漏场景
如果输出的 goroutines 数量是在不断增加的,就说明存在泄漏
nil channel
channel 如果忘记初始化,那么无论你是读,还是写操作,都会造成阻塞。
发送不接收 channel 发送数量 超过 channel接收数量,就会造成阻塞
接收不发送 channel 接收数量 超过 channel发送数量,也会造成阻塞
http request body未关闭 resp.Body.Close() 未被调用时,goroutine不会退出
互斥锁忘记解锁 第一个协程获取 sync.Mutex 加锁了,但是他可能在处理业务逻辑,又或是忘记 Unlock 了。 因此导致后面的协程想加锁,却因锁未释放被阻塞了
sync.WaitGroup使用不当 由于 wg.Add 的数量与 wg.Done 数量并不匹配,因此在调用 wg.Wait 方法后一直阻塞等待
如何排查
单个函数:调用 runtime.NumGoroutine 方法来打印 执行代码前后Goroutine 的运行数量,进行前后比较,就能知道有没有泄露了
生产/测试环境:使用PProf实时监测Goroutine的数量
4、如何查看正在执行的goroutine数量
程序中引入pprof pakage,分析goroutine文件
5、如何控制并发的goroutine数量
有缓冲channel
利用缓冲满时发送阻塞的特性
无缓冲channel
任务发送和执行分离,指定消费者并发协程数
什么时候抢占P
1.如果存在系统调用超时:存在超过 1 个 sysmon tick 周期(至少 20us)的任务,则会从系统调用中抢占 P。 2.如果没有空闲的 P:所有的 P 都已经与 M 绑定。需要抢占当前正处于系统调用之,而实际上系统调用并不需要的这个 P 的情况,会将其分配给其它 M 去调度其它 G。 3.如果 P 的运行队列里面有等待运行的 G,为了保证 P 的本地队列中的 G 得到及时调度。而自己本身的 P 又忙于系统调用,无暇管理。此时会寻找另外一个 M 来接管 P,从而实现继续调度 G 的目的。
关于启动流程
启动流程
g0
g 一般分为三种,分别是: 执行用户任务的叫做 g。 执行 runtime.main 的 main goroutine。 执行调度任务的叫 g0。。 g0 比较特殊,每一个 m 都只有一个 g0(仅此只有一个 g0),且每个 m 都只会绑定一个 g0。在 g0 的赋值上也是通过汇编赋值的,其余后续所创建的都是常规的 g。 从多个方面来看: 数据结构:g0 和其他创建的 g 在数据结构上是一样的,但是存在栈的差别。在 g0 上的栈分配的是系统栈,在 Linux 上栈大小默认固定 8MB,不能扩缩容。而常规的 g 起始只有 2KB,可扩容。 运行状态:g0 和常规的 g 不一样,没有那么多种运行状态,也不会被调度程序抢占,调度本身就是在 g0 上运行的。 变量声明:g0 和常规 g,g0 的定义就是 var g0 g,没什么特别之处。
m0
m0 是 Go Runtime 所创建的第一个系统线程,一个 Go 进程只有一个 m0,也叫主线程。 从多个方面来看: 数据结构:m0 和其他创建的 m 没有任何区别。 创建过程:m0 是进程在启动时应该汇编直接复制给 m0 的,其他后续的 m 则都是 Go Runtime 内自行创建的。 变量声明:m0 和常规 m 一样,m0 的定义就是 var m0 m,没什么特别之处。
goroutine挂起
通道Channel
垃圾回收GC
休眠Sleep
锁等待Lock
抢占Preempted
IO阻塞IO Wait
其他:panic、finalizer、select