返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

4.4.3 新建

发布于 2024-10-12 19:16:05 字数 3028 浏览 0 评论 0 收藏 0

新建 M 包装对象,分配 g0 栈。

创建系统线程(thread),执行线程函数 mstart,进入调度循环。

// runtime2.go

type m struct {
    g0        *g        // goroutine with scheduling stack
    p         puintptr  // attached p for executing go code (nil if not executing go code)
    mstartfn  func()
}
// proc.go

// Create a new m. It will start off with a call to fn, or else the scheduler.

func newm(fn func(), _p_ *p, id int64) {
    mp := allocm(_p_, fn, id)
    mp.nextp.set(_p_)
    newm1(mp)
}

每个 M 都自带 g0(默认 8KB),用于执行运行时命令。

某些操作系统由内核提供 g0 内存。

新建 M 加入 allm 链表,总数不能超过限制(默认 10000)。

// Allocate a new m unassociated with any thread.
// Can use p for allocation context if needed.
// fn is recorded as the new m's m.mstartfn.

func allocm(_p_ *p, fn func(), id int64) *m {
    mp := new(m)
    mp.mstartfn = fn
    
    // 设置 id,保存到 allm 链表等。
    // 检查是否超过 sched.maxmcount 限制。
    mcommoninit(mp, id)
    
    // In case of cgo or Solaris or illumos or Darwin, pthread_create will make us a stack.
    // Windows and Plan 9 will layout sched stack on OS stack.
    if iscgo || GOOS == "solaris" || GOOS == "illumos" || GOOS == "windows" || GOOS == "plan9" || GOOS == "darwin" {
        mp.g0 = malg(-1)
    } else {
        mp.g0 = malg(8192 * sys.StackGuardMultiplier)
    }
    
    return mp
}
func newm1(mp *m) {
    newosproc(mp)
}

不同操作系统创建线程的方式不同,不过启动函数均为 mstart。

// os_linux.go

const cloneFlags = _CLONE_VM |        /* share memory */
                   _CLONE_FS |        /* share cwd, etc */
                   _CLONE_FILES |     /* share fd table */
                   _CLONE_SIGHAND |   /* share sig handler table */
                   _CLONE_SYSVSEM |   /* share SysV semaphore undo lists (see issue #20763) */
                   _CLONE_THREAD      /* revisit - okay for now */

func newosproc(mp *m) {
    stk := unsafe.Pointer(mp.g0.stack.hi)
    ret := clone(cloneFlags, stk, unsafe.Pointer(mp), unsafe.Pointer(mp.g0), unsafe.Pointer(funcPC(mstart)))
}
// os_windows.go

func newosproc(mp *m) {
    thandle := stdcall6(_CreateThread, 0, 0, funcPC(tstart_stdcall), uintptr(unsafe.Pointer(mp)), 0, 0)
}

// sys_windows_amd64.s 
TEXT runtime·tstart_stdcall(SB),NOSPLIT,$0
    CALL    runtime·mstart(SB)

mstart

线程启动后,进入 schedule 调度循环。

注意:

startm 由 wakep 调用,用于新建或唤醒。

mstart 是线程函数。创建线程后执行,进入调度循环。

正常情况下,M 一直在调度循环。

只有在出错时,才会回到 mstart,终止线程。

详情参考《其他》。

// proc.go

// Called to start an M.

func mstart() {
    mstart1()
    
    // Exit this thread.
    mexit(osStack)
}
func mstart1() {
    
    // 执行初始化函数(startm 传入的是设置自旋状态 mspinning)
    if fn := _g_.m.mstartfn; fn != nil {
        fn()
    }
    
    // 不是主线程。
    if _g_.m != &m0 {
        // 从 nextp 提取并绑定 P。
        acquirep(_g_.m.nextp.ptr())
        _g_.m.nextp = 0
    }
    
    // 进入调度循环。
    schedule()
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文