goroutine concurrent

75 阅读1分钟

g 有一个 sched 的结构体,保留了寄存器:

type gobuf struct {
    sp   uintptr
    pc   uintptr
    g    guintptr
    ctxt unsafe.Pointer
    ret  uintptr
    lr   uintptr
    bp   uintptr
}

正常我们可以通过 bp 进行调用栈的异步展开。

也就是说,可以通过一个 runtime.foreachg 函数,获取所有正在运行的 goroutine 的堆栈。

但这是不可行的。

正在运行的 goroutine 的堆栈,必须该 goroutine 非 running 状态才能获取堆栈,否则 bp 寄存器之类的可能为空。

// doRecordGoroutineProfile writes gp1's call stack and labels to an in-progress
// goroutine profile. Preemption is disabled.
//
// This may be called via tryRecordGoroutineProfile in two ways: by the
// goroutine that is coordinating the goroutine profile (running on its own
// stack), or from the scheduler in preparation to execute gp1 (running on the
// system stack).
func doRecordGoroutineProfile(gp1 *g) {
    println(readgstatus(gp1)) // waiting 或 runnable 状态。
    if readgstatus(gp1) == _Grunning {
       print("doRecordGoroutineProfile gp1=", gp1.goid, "\n")
       throw("cannot read stack of running goroutine")
    }

    offset := int(goroutineProfile.offset.Add(1)) - 1

    if offset >= len(goroutineProfile.records) {
       // Should be impossible, but better to return a truncated profile than
       // to crash the entire process at this point. Instead, deal with it in
       // goroutineProfileWithLabelsConcurrent where we have more context.
       return
    }

    // saveg calls gentraceback, which may call cgo traceback functions. When
    // called from the scheduler, this is on the system stack already so
    // traceback.go:cgoContextPCs will avoid calling back into the scheduler.
    //
    // When called from the goroutine coordinating the profile, we still have
    // set gp1.goroutineProfiled to goroutineProfileInProgress and so are still
    // preventing it from being truly _Grunnable. So we'll use the system stack
    // to avoid schedule delays.
    systemstack(func() { saveg(^uintptr(0), ^uintptr(0), gp1, &goroutineProfile.records[offset]) })
}

如何 concurrent 获取 goroutine 的堆栈:

1,stop the world

2, runnable 状态的 goroutine 会去 tryRecordGoroutineProfile。

3, waiting 状态的 goroutine 会通过 foreachg 去获取调用栈。

// Schedules gp to run on the current M.
// If inheritTime is true, gp inherits the remaining time in the
// current time slice. Otherwise, it starts a new time slice.
// Never returns.
//
// Write barriers are allowed because this is called immediately after
// acquiring a P in several places.
//
//go:yeswritebarrierrec
func execute(gp *g, inheritTime bool) {
    mp := getg().m

    if goroutineProfile.active {
       // Make sure that gp has had its stack written out to the goroutine
       // profile, exactly as it was when the goroutine profiler first stopped
       // the world.
       tryRecordGoroutineProfile(gp, osyield)
    }