美文网首页
golang 源码剖析(1): 运行初始化和包初始化

golang 源码剖析(1): 运行初始化和包初始化

作者: darcyaf | 来源:发表于2020-03-03 19:30 被阅读0次

    初始化中主要对命令行参数整理,环境变量设置,以及内存分配器,垃圾回收器,并发调度器的工作现场准备

    基本概念

    • 传统并发使用的是:多线程共享内存,go 使用的是CSP(communicating sequential processes)并发模型,以通信的方式来共享内存.
      go 中使用GPM方式来实现CSP,每个M关联一个P,,goroutine关联哪个P是无法控制的,P中维护了一个goroutin的列表,并用循环的方式取出一个G来关联上P来执行程序.
      // The main concepts are:
      // G - goroutine.
      // M - worker thread, or machine.
      // P - processor, a resource that is required to execute Go code.
      // M must have an associated P to execute Go code, however it can be
      // blocked or in a syscall w/o an associated P.

    入口

    随便写个demo debug,调用栈如下

    main.main at main.go:15
    runtime.main at proc.go:203
    runtime.goexit at asm_amd64.s:1357
     - Async stack trace
    runtime.rt0_go at asm_amd64.s:220
    

    即go进程先启动runtime.main,然后才执行main.main
    然后就可以新建一个G和M 开始运行程序.
    runtime/asm_amd64.s

    // The new G calls runtime·main.
    
        call    runtime·args(sb)
        call    runtime·osinit(sb)
        call    runtime·schedinit(sb)
    
        // create a new goroutine to start program
        MOVQ    $runtime·mainPC(SB), AX     // entry
    
        // start this M
        CALL    runtime·mstart(SB)
    在runtime
    

    scheduinit 这里启动了一个M(最大10000个),ncpu个P,初始化了一系列东西

    // The bootstrap sequence is:
    //
    //  call osinit
    //  call schedinit
    //  make & queue new G
    //  call runtime·mstart
        tracebackinit()
        moduledataverify()
        stackinit()
        mallocinit()
        mcommoninit(_g_.m)
        cpuinit()       // must run before alginit
        alginit()       // maps must not be used before this call
        modulesinit()   // provides activeModules
        typelinksinit() // uses maps, activeModules
        itabsinit()     // uses activeModules
    
        msigsave(_g_.m)
        initSigmask = _g_.m.sigmask
    
        goargs()
        goenvs()
        parsedebugvars()
        gcinit()
    
        sched.lastpoll = uint64(nanotime())
        procs := ncpu
        if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
            procs = n
        }
        if procresize(procs) != nil {
            throw("unknown runnable goroutine during bootstrap")
        }
    

    包init流程

    src/cmd/compile/internal/gc/noder.go这个路径下,这里会对先解析所有的文件
    gc.go中lines := parseFiles(flag.Args())调用解析文件函数,最终会生成一个pkgMap:map[string]*Pkg
    接着查找所有的init方法并改名为init.0,init.1累加的名字,屏蔽了init函数防止调用,生成一个.inittask方法方便调用
    在gc/init.go中

        nf := initOrder(n) //检查初始化时的包循环引用等问题
    
        // Record user init functions. 为函数生成别名
        for i := 0; i < renameinitgen; i++ {
            s := lookupN("init.", i)
            fns = append(fns, s.Linksym())
        }
        // Make an .inittask structure.
        sym := lookup(".inittask")
        nn := newname(sym)
    

    src/runtime/proc.go中:

        //go:linkname runtime_inittask runtime..inittask
        var runtime_inittask initTask
        //go:linkname main_inittask main..inittask
        var main_inittask initTask
        doInit(&runtime_inittask) // must be before defer
        doInit(&main_inittask)
        fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
        fn()
    

    由此调用init之后再进入main.main入口

    包init顺序

    go中建议对包的引用只做初始化,不涉及包的顺序
    先初始化依赖包,然后对同一个包下多文件按字母顺序初始化,同一文件从上到下init
    src/cmd/compile/internal/gc/initorder.go中

    // Assignments are in one of three phases: NotStarted, Pending, or
    // Done. For assignments in the Pending phase, we use Xoffset to
    // record the number of unique variable dependencies whose
    // initialization assignment is not yet Done. We also maintain a
    // "blocking" map that maps assignments back to all of the assignments
    // that depend on it.
    //
    // For example, for an initialization like:
    //
    //     var x = f(a, b, b)
    //     var a, b = g()
    //
    // the "x = f(a, b, b)" assignment depends on two variables (a and b),
    // so its Xoffset will be 2. Correspondingly, the "a, b = g()"
    // assignment's "blocking" entry will have two entries back to x's
    // assignment.
    

    这里的例子中,如果x依赖于a和b,那么x.off+=2,然后将x 添加到blocking[a]和blocking[b]的列表中去。
    当a,b初始化完了以后会将x的offset--1。
    最后再判断offset,如果offset不为0的话说明发生了循环调用。

    相关文章

      网友评论

          本文标题:golang 源码剖析(1): 运行初始化和包初始化

          本文链接:https://www.haomeiwen.com/subject/qkdhlhtx.html