go如何启动一个goroutine

 


1 准备工作


    1.1 创建一个main.go

复制代码
package main

import "fmt"

type ss struct {
        a_b string
        b   int64
}

func x(c chan int) {
        b :{"", 4}
        if b.a_b != "" {
                return
        }
        c<-233
        return
}
func main() {
        var c1 chan int
        c1 = make(chan int, 3)
        go x(c1)
        fmt.Printf("%d", <-c1)
}
复制代码

     执行编译:go build -gcflags="all=-N -l"  main.go

   1.2 dlv执行调试

dlv exec main

2 goroutine的创建


    2.1 单步调试go func()

      将断点打到 go x(c1) 处

(dlv) bp
Breakpoint runtime-fatal-throw (enabled) at 0x437a40,0x437940 for (multiple functions)() <multiple locations>:0 (0)
Breakpoint unrecovered-panic (enabled) at 0x437da0 for runtime.fatalpanic() /usr/local/go/src/runtime/panic.go:1141 (0)
        print runtime.curg._panic.arg
Breakpoint 23 (enabled) at 0x49e58f for main.main() ./main.go:18 (1)
Breakpoint 24 (enabled) at 0x49e5ca for main.main() ./main.go:21 (1)

    2.2 goroutine 的唤醒

    单步调试到创建goroutine处

复制代码
> main.main() ./main.go:21 (PC: 0x49e61a)
        main.go:21      0x49e609        eb0a                    jmp 0x49e615
        main.go:21      0x49e60b        4889d7                  mov rdi, rdx
        main.go:21      0x49e60e        e82d82fcff              call $runtime.gcWriteBarrierCX
        main.go:21      0x49e613        eb00                    jmp 0x49e615
        main.go:21      0x49e615        488b442448              mov rax, qword ptr [rsp+0x48]
=>      main.go:21      0x49e61a        e8413dfaff              call $runtime.newproc
        main.go:22      0x49e61f        48c744242800000000      mov qword ptr [rsp+0x28], 0x0
        main.go:22      0x49e628        488b442430              mov rax, qword ptr [rsp+0x30]
        main.go:22      0x49e62d        488d5c2428              lea rbx, ptr [rsp+0x28]
        main.go:22      0x49e632        e86979f6ff              call $runtime.chanrecv1
        main.go:22      0x49e637        440f117c2458            movups xmmword ptr [rsp+0x58], xmm15
复制代码

    将断点打到runtime.newproc 函数中的  if mainStarted 处,当继续单步时发现调用了wakep() 函数

复制代码
(dlv) l 4246
Showing /usr/local/go/src/runtime/proc.go:4246 (PC: 0x0)
  4241: func newproc(fn *funcval) {
  4242:         gp := getg()
  4243:         pc := getcallerpc()
  4244:         systemstack(func() {
  4245:                 newg := newproc1(fn, gp, pc)  // 创建 g
  4246:
  4247:                 pp := getg().m.p.ptr()
  4248:                 runqput(pp, newg, true)       // 将g放入运行队列
  4249:
  4250:                 if mainStarted {
=>4251: wakep() // 唤醒或者创建一个新的m和系统线程绑定p运行 g 4252: } 4253: }) 4254: } 4255: 4256: // Create a new g in state _Grunnable, starting at fn. callerpc is the (dlv) bp Breakpoint runtime-fatal-throw (enabled) at 0x437940,0x437a40 for (multiple functions)() <multiple locations>:0 (0) Breakpoint unrecovered-panic (enabled) at 0x437da0 for runtime.fatalpanic() /usr/local/go/src/runtime/panic.go:1141 (0) print runtime.curg._panic.arg Breakpoint 29 (enabled) at 0x49e58f for main.main() ./main.go:18 (1) Breakpoint 30 (enabled) at 0x49e5ca for main.main() ./main.go:21 (1) Breakpoint 31 (enabled) at 0x44242c for runtime.newproc.func1() /usr/local/go/src/runtime/proc.go:4250 (1)
复制代码

可以发现当主线程也就是初始化main goroutine 运行main.main 的线程在将mainStarted设置为true后

复制代码
// The main goroutine.
func main() {
    mp := getg().m
    ....
    // Allow newproc to start new Ms.
    mainStarted = true  // 将全局变量 mainStarted 设置true
        
    ....
}
复制代码

    后续执行go func() 时执行 newproc 新建 goroutine 完成后,直接尝试wakep唤醒一个空闲的p 开启执行了

3、P的唤醒


3.1 获取空闲p

     可以发现当主线程运行起来main goroutine 后,每当有新的goroutine创建都会尝试调用wakep 唤醒p

    在wakep中会尝试去sched.pidle 全局调度管理变量sched 的空闲p链表获取空闲的p然后绑定m运行

复制代码
// Tries to add one more P to execute G's.
// Called when a G is made runnable (newproc, ready).
// Must be called with a P.
func wakep() {
    // Be conservative about spinning threads, only start one if none exist
    // already.
    if sched.nmspinning.Load() != 0 || !sched.nmspinning.CompareAndSwap(0, 1) {
        return
    }

    // Disable preemption until ownership of pp transfers to the next M in
    // startm. Otherwise preemption here would leave pp stuck waiting to
    // enter _Pgcstop.
    //
    // See preemption comment on acquirem in startm for more details.
    mp := acquirem()

    var pp *p
    lock(&sched.lock)
    pp, _ = pidlegetSpinning(0)
    if pp == nil {
        if sched.nmspinning.Add(-1) < 0 {
            throw("wakep: negative nmspinning")
        }
        unlock(&sched.lock)
        releasem(mp)
        return
    }
    // Since we always have a P, the race in the "No M is available"
    // comment in startm doesn't apply during the small window between the
    // unlock here and lock in startm. A checkdead in between will always
    // see at least one running M (ours).
    unlock(&sched.lock)

    startm(pp, true, false)    // 将空闲p 传递过去运行

    releasem(mp)
}
复制代码

3.2 开启一个m

startm 接收一个空闲p,获取一个空闲的m(没有就新建m 创建系统线程)绑定运行
复制代码
func startm(pp *p, spinning, lockheld bool) {
    mp := acquirem()
    if !lockheld {
        lock(&sched.lock)
    }
    if pp == nil {
        if spinning {
            // TODO(prattmic): All remaining calls to this function
            // with _p_ == nil could be cleaned up to find a P
            // before calling startm.
            throw("startm: P required for spinning=true")
        }
        pp, _ = pidleget(0)
        if pp == nil {
            if !lockheld {
                unlock(&sched.lock)
            }
            releasem(mp)
            return
        }
    }
    nmp := mget()
    if nmp == nil {

        id := mReserveID()
        unlock(&sched.lock)

        var fn func()
        if spinning {
            // The caller incremented nmspinning, so set m.spinning in the new M.
            fn = mspinning
        }
        newm(fn, pp, id)  // 新建m开启运行

        if lockheld {
            lock(&sched.lock)
        }
        releasem(mp)
        return
    }
    if !lockheld {
        unlock(&sched.lock)
    }
    if nmp.spinning {
        throw("startm: m is spinning")
    }
    if nmp.nextp != 0 {
        throw("startm: m has p")
    }
    if spinning && !runqempty(pp) {
        throw("startm: p has runnable gs")
    }
    // The caller incremented nmspinning, so set m.spinning in the new M.
    nmp.spinning = spinning
    nmp.nextp.set(pp)      // 将当前M要运行的p设定为传递过来的空闲p
    notewakeup(&nmp.park)  // 唤醒通过futex 调用挂在 m.park.key 上的系统线程(唤醒M、唤醒睡眠的线程)
    releasem(mp)
}
复制代码

可以看见当存在空闲P时,就会尝试去获取(创建)一个M来运行P(g),

4、创建系统线程(LWP)


    4.1 空闲P的调用栈

    调用栈为 go func() -> newproc() -> wakep() -> startm() -> newm() -> newm1() -> newosproc()

复制代码
> runtime.newosproc() /usr/local/go/src/runtime/os_linux.go:167 (PC: 0x433e3d)
Warning: debugging optimized function
   162:
   163: // May run with m.p==nil, so write barriers are not allowed.
   164: //
   165: //go:nowritebarrier
   166: func newosproc(mp *m) {
=> 167:         stk := unsafe.Pointer(mp.g0.stack.hi)
   168:         /*
   169:          * note: strace gets confused if we use CLONE_PTRACE here.
   170:          */
   171:         if false {
   172:                 print("newosproc stk=", stk, " m=", mp, " g=", mp.g0, " clone=", abi.FuncPCABI0(clone), " id=", mp.id, " ostk=", &mp, "\n")
(dlv) stack
 0  0x0000000000433e3d in runtime.newosproc
    at /usr/local/go/src/runtime/os_linux.go:167
 1  0x000000000043e150 in runtime.newm1
    at /usr/local/go/src/runtime/proc.go:2253
 2  0x000000000043e025 in runtime.newm
    at /usr/local/go/src/runtime/proc.go:2228
 3  0x000000000043e539 in runtime.startm
    at /usr/local/go/src/runtime/proc.go:2411
 4  0x000000000043e9d4 in runtime.wakep
    at /usr/local/go/src/runtime/proc.go:2541
 5  0x000000000044243a in runtime.newproc.func1
    at /usr/local/go/src/runtime/proc.go:4251
 6  0x0000000000464689 in runtime.systemstack
    at /usr/local/go/src/runtime/asm_amd64.s:496
 7  0x0000000000464620 in runtime.systemstack_switch
    at /usr/local/go/src/runtime/asm_amd64.s:463
 8  0x00000000004423b1 in runtime.newproc
    at /usr/local/go/src/runtime/proc.go:4244
 9  0x000000000043a2a5 in runtime.init.6
    at /usr/local/go/src/runtime/proc.go:293
10  0x0000000000447852 in runtime.doInit
    at /usr/local/go/src/runtime/proc.go:6506
11  0x0000000000439ff3 in runtime.main
    at /usr/local/go/src/runtime/proc.go:199
12  0x0000000000466701 in runtime.goexit
    at /usr/local/go/src/runtime/asm_amd64.s:1598
复制代码

    

    关于新的M是如何创建绑定p与g 的开始运行可以查看此文:系统调用clone 创建新的系统线程  执行mstart 运行调度器

    一篇更为详细的工作线程唤醒及其创建文章

posted @   G1733  阅读(6)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示