• golang笔记 mutex,抢占式调度,semaphore


    Mutex

    正常模式 饥饿模式

    在这里插入图片描述
    在这里插入图片描述
    一个goroutine获得锁的过程
    1,g先自旋几次尝试获得锁,失败后进入等待队列排队等待

    自旋 /等待队列

    在这里插入图片描述

    2 等待队列前面的g在排到的时候先要与自旋中的g竞争
    3 而等待队列中的g大概率拿不到锁,它会被放到原来的位置(头部)继续等待
    4 当这个g等待超过1ms后,会将mutex从正常模式切换到饥饿模式
    5 mutex在饥饿模式下,不在执行自旋,所有的goroutine都要排队,先进先出
    在这里插入图片描述
    以上
    1正常模式可以保证高并发,但是有可能出现尾端延迟
    2饥饿模式则解决的尾端延迟的问题,但牺牲了一定的性能

    lock和unlock

    在这里插入图片描述

    1第一位记录是否加锁
    2第二位记录是否有g被唤醒
    3mutex工作模式 0正常模式,1jie模式
    4 其它位记录有多少个等待着
    在这里插入图片描述
    lock()和unlock()方法调用了atomic操作,对mutexwritershift位的数据原子的修改,来获得锁或取消锁
    在这里插入图片描述
    在这里插入图片描述

    1自旋状态的g尝试修改state中的mutexwoken位,以告诉持有锁的g在unlock时已经有g被唤醒了,不要再去唤醒其他g了
    2每个g自旋4次,每次自旋前都要判断自旋次数,有没有释放锁,或者进入饥饿模式,
    3自旋结束后通过atomic操作state,成功则是抢到锁或唤醒标识位进入排队,失败则从头再来
    3如果等待1ms还没有拿到锁,就将mutex设置成饥饿模式

    抢占式调度

    linux下查看goroutine
    1 top命令查到pid
    在这里插入图片描述
    2 对于一个正在运行的程序,可以用dlv attach $pid来追踪

    在这里插入图片描述
    3 grs命令查看当前所有的协程
    在这里插入图片描述

    在这里插入图片描述
    来分析下grs输出的欣信息:
    1 goroutine1和goroutine6是我代码创建的,其余是runtime创建的,
    2goroutine对应代码第12行,即mian.mian
    3 goroutine6对应第9行,
    4 goroutin前面的星号表示当前调试绑定1号协程

    在这里插入图片描述

    gr 6 切换到6号协程
    在这里插入图片描述
    再通过bt栈回溯
    在这里插入图片描述
    是STW导致阻塞,STW要抢占所有的p,使其让出cpu,让gc开始工作

    在这里插入图片描述
    1,gc记录当前需要等待多少个p

    在这里插入图片描述

    2 当前p,系统正在调用的p,以及空闲的p,将其设置成pgcstop即可,对于当前正在运行的G的P,将其stackguard设置成stackpreement,标识gc正在等待让出,gcwaiting=1
    在这里插入图片描述
    具体是如何抢占呢?
    1,前面了解了函数在编译阶段编译器会在函数头部插入检测代码,检测函数是否需要栈增长,档期设置成stackpreempt时即表示不会栈增长,而是执行schedule,调度时先检测gcwaiting的标识,若=1,则让出让gc开始工作
    在这里插入图片描述
    之所以出现上述问题,是因为代码中for只是空循环,也就不会插入栈增长代码,当然不会去检测gcwaiting

    发送sigpreempt信号

    在这里插入图片描述
    抢占实现:
    1 函数preemptone实现抢占p,4个步骤
    a将g.preempt设置为ture
    b 将g.startguard0设置成stackpreempt
    c 判断硬件支持
    d 判断用户是否允许,以上判断通过则将p.preempt设置成true
    2 实际的抢占工作交由preemptM函数完成
    3 preempt函数通过调用runtime.signalM发送信号给特定的M
    4 发送信号调用了系统调用的SYS_tgkill发送给目标线程
    以上实现了抢占的前半部分工作–信号发送,后面的工作有接受到信号的m完成

    sighandler处理信号

    在这里插入图片描述

    1 m接受到sigpreenpt信号,交给sighandler来处理信号
    2 sighandler函数确认信号为preempt后调用dosigpreempt函数,它会判断是否对指定的G异步抢占,通过:
    a 指定的G与其对应的p,m的preempt字段都为true
    b 指定的g处在grunning状态
    c 指定的g可以安全的挂起,并对她进行栈扫描,没有打断写屏障
    d 有足够的空间注入异步抢占函数
    e 当前没有runtime相关的锁,不会引起死锁
    3以上确认了要抢占,并且抢占式安全的,调用pushcall注入异步抢占函数
    4 异步抢占函数asuncpreempt函数先保存现场,调用runtime.asyncpreempt2函数最终去执行schedule

    在这里插入图片描述

    所以查看到在go1.14之后,既是for空循环,也调用了asyncpreempt2函数
    在这里插入图片描述

  • 相关阅读:
    Error: svn: E155004: Run ‘svn cleanup‘ to remove locks
    基于STM32与ESP8266的太空人WiFi天气时钟(代码开源)
    【Linux】进程篇(补):守护进程
    Java语言Map中GetOrDefault方法的使用
    GUI编程--PyQt5--QPushButton
    C++中的多态
    MySQL筑基篇之增删改查
    win10中ros与ubuntu中ros通信
    Vue获取methods中方法的return返回值
    【工具篇】Unity翻书效果的三种方式
  • 原文地址:https://blog.csdn.net/weixin_41479678/article/details/125498640