• go笔记之——goroutine


    goroutine底层实现原理!!!!!!!!!!!!!

    概念

    Goroutine可以理解为一种Go语言的协程(轻量级线程),是Go支持高并发的基础,属于用户态的线程,由Goruntime管理而不是操作系统。

    底层数据结构存储了非常多的上下文:

    底层数据结构:

    在这里插入图片描述

    最终是一个runtime.g对象放入了队列

    状态流转:

    在这里插入图片描述

    状态轮转图

    在这里插入图片描述

    1.创建

    通过go关键字调用底层函数runtime.newproc()创建一个goroutine当调用该函数之后goroutine会被设置成runnable状态

    func main( ) {
    go func() {
    fmt.Println("func routine"")
    }()
    fmt.Println("main goroutine")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    创建好的这个goroutine会新建一个自己的栈空间,同时在G的sched中维护栈地址与程序计数器这些信息。每个G在被创建之后,都会被优先放入到本地队列中,如果本地队列已经满了,就会被放入到全局队列中。

    2.运行

    goroutine本身只是一个数据结构,真正让goroutine运行起来的是调度器。Go实现了一个用户态的调度器(GMP模型),这个调度器充分利用现代计算机的多核特性,同时让多个goroutine运行,同时goroutine设计的很轻量级,调度和上下文切换的代价都比较小。

    在这里插入图片描述

    3.调度时机

    调度时机

    新起一个协程和协程执行完毕

    会阻塞的系统调用,比如文件io、网络io. channel、mutex等阻塞操作

    time.sleep

    垃圾回收之后主动调用runtime.Gosched()·运行过久或系统调用过久等等

    先本地g执行,执行完全局拿(每次全局拿记得上锁,防止被多次拿),全局拿玩就去其他本地队列偷,每次偷一半(下取整),如果没得偷的了就自旋,每次最多有设定好的个数自旋等待新的任务,防止cpu浪费资源

    4.阻塞

    channel的读写操作、等待锁、等待网络数据、系统调用等都有可能发生阻塞,会调用底层函
    数runtime.gopark(),会让出CPU时间片,让调度器安排其它等待的任务运行,并在下次某个时候从该位置恢复执行。

    当调用该函数之后,goroutine会被设置成waiting状态

    5.唤醒

    处于waiting状态的goroutine,在调用runtime.goready()函数之后会被唤醒,唤醒的goroutine会被重新放到M对应的上下文P对应的runqueue中,等待被调度。

    当调用该函数之后,goroutine会被设置成runnable状态

    6.退出

    当goroutine执行完成后,会调用底层函数runtime.Goexit()。当调用该函数之后,goroutine会被设置成dead 状态


    goroutine和线程的区别

    在这里插入图片描述


    goroutine泄露场景

    泄漏原因
    1.Goroutine内进行channel/mutex等读写操作被一直阻塞。
    2.Goroutine内的业务逻辑进入死循环,资源一直无法释放。
    3.Goroutine内的业务逻辑进入长时间等待,有不断新增的Goroutine进入等待

    泄露场景
    1.如果输出的goroutines数量是在不断增加的,就说明存在泄漏
    2.nil channel,对空channel读写
    3.channel如果忘记初始化,那么无论你是读,还是写操作,都会造成阻塞。

    1.nil channel

    func main() {
    fmt.Println("before goroutines: ",runtime.NumGoroutine( ) )
    block1()
    time.Sleep(time.Second * 1)
    fmt.Println("after goroutines: ", runtime.NumGoroutine()
    }
    
    func block1() {
    var ch chan int
    for i := 0; i < 10; i++ {
    go func() {
    <-ch3}()
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    2.接受不发送

    channel发送数量草果channel接收数量,造成了阻塞

    func block2() {
    ch := make( chan int)
    for i := 0; i < 10; i++ {
    go func() {
    ch <- 1}()
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.只接受不发送

    channel接收数量超过了channelf发送的数量,也会造成阻塞

    func block3( ) {
    ch := make(chan int)
    for i := 0; i < 10; i++ {
    go func() {
    <-ch}()
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.http request body未关闭

    resp.Body.Close(),为被调用,groutine不会退出

    5.互斥锁忘记解锁
    第一个协程获取sync.Mutex加锁了,但是他可能在处理业务逻辑,又或是忘记Unlock 了。
    因此导致后面的协程想加锁,却因锁未释放被阻塞了

    func block5() {
    var mutex sync.Mutex
    for i := 0; i < 10; i++ {
    go func() {
    mutex.Lpck()}()
    }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6.sync.WaitGroup原语使用不当

    由于wg.Add的数量与wg.Done 数量并不匹配,因此在调用wg.wait方法后一直阻塞等待

    func block6() {
    var wg sync.waitGroup
    for i := 0; i < 10; i++ {
    go func() {
    wg.Add(2)
    wg.Done()
    wg.wait()}()
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    如何排查

    1.单个函数︰调用runtime.NumGoroutine方法来打印执行代码前后Goroutine的运行数量,进行前后比较,就能知道有没有泄露了。

    2.生产/测试环境:使用PProf 实时监测Goroutine的数量


    如何查看正在执行的goroutine的数量?

    因为go服务一般是在线服务,所以我们引入pprof package

    //在程序中引入pprof package
    
    import _ "net/http/pprof"
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    在这里插入图片描述


    如何控制并发的goroutine数量

    为什么要控制goroutine并发的数量?

    在开发过程中,如果不对goroutine加以控制而进行滥用的话,可能会导致服务整体崩溃。比如耗尽系统资源导致程序崩溃,或者CPU使用率过高导致系统忙不过来。

    用什么方法控制goroutine并发的数量?

    有缓冲channel利用缓冲满时发送阻塞的特性

    在这里插入图片描述

    有数据才创建!!!!!!!!!!!!这里最多支持三个goroutine

  • 相关阅读:
    Redis缓存使用技巧和设计方案
    Linux:kubernetes(k8s)探针LivenessProbe的使用(9)
    Java基础语法
    浅谈ChatGPT
    Elasticsearch/Kibana安装
    电子邮件发送接收原理(附 go 语言实现发送邮件)
    代码随想录算法训练营 day46|139.单词拆分
    友宝在线在港交所上市申请“失效”:连续两年亏损,王滨为大股东
    3线硬件SPI驱动 HX8347 TFT屏
    java基于微信小程序的在线学习辅导系统 uinapp 计算机毕业设计
  • 原文地址:https://blog.csdn.net/qq_52563729/article/details/126105806