首页 > 其他分享 >go启动流程(一) main函数的执行

go启动流程(一) main函数的执行

时间:2024-01-30 14:11:57浏览次数:22  
标签:runtime 函数 g0 流程 newg _. go main

go版本:go1.19

操作系统:linux

系统架构:amd64

go version go1.19 linux/amd64

本文主要分析在go程序中,编写的main函数是如何被执行的。

流程总览图

启动流程
启动流程

从程序执行入口开始

利用gdb确定程序执行入口

  1. 编写一个简单的go程序

    //main.go
    package main

    import "fmt"

    func main() {
       fmt.Println("start")
    }
  2. 将程序编译成可执行文件

    go build -gcflags "-N -l" main.go
  3. gdb调试

    gdb main
    gdb调试
    gdb调试

    输入info files可以看到main文件的执行入口是0x45bfa0

    在0x45bfa0处打一个断点(注意有个*号),可以看到程序从rt0_linux_amd64.s文件的第8行开始执行

  4. 扔掉gdb,从汇编函数开始分析

从汇编函数开始

go的汇编代码是通过plan9汇编写的

rt0_linux_amd64.s文件

rt0_linux_amd64
rt0_linux_amd64

这里可以看到执行了一个JMP指令,执行到_rt0_amd64函数

_rt0_amd64函数

rt0_amd64
rt0_amd64
TEXT _rt0_amd64(SB),NOSPLIT,$-8
    MOVQ    0(SP), DI   // DI = argc,argc表示命令行参数的个数
    LEAQ    8(SP), SI   // SI = argv,argv表示命令行的值
    JMP runtime·rt0_go(SB)      //跳转到rt0_go

保存两个命令行参数到寄存器中,相当于c++中的main函数int main(int argc,char **argv)

在go程序中,可以用os.argv获取argv的值,如go run xxx.go a b c d,argc = 5,argv=[/{{file_root}}/xxx.go,a,b,c,d]

rt0_go函数

逐行分析这个函数

栈相关

  • 保存命令行参数到栈中
  • 设置g0的堆栈保护
TEXT runtime·rt0_go(SB),NOSPLIT|TOPFRAME,$0
    // copy arguments forward on an even stack
    MOVQ    DI, AX      // AX = argc
    MOVQ    SI, BX      // BX = argv
    SUBQ    $(5*8), SP     // SP -= 40     栈顶向下扩展40字节
    ANDQ    $~15, SP   //SP = SP & 10000,将SP的后四位置为0,内存地址16字节对齐,后4位必须为0
    MOVQ    AX, 24(SP)  // SP + 24 = argc = AX(表示距SP地址24字节处,保存AX的值)
    MOVQ    BX, 32(SP)  // SP + 32 = argv = BX      将命令行参数保存到线程栈中

    // create istack out of the given (operating system) stack.
    // _cgo_init may update stackguard.
    MOVQ    $runtime·g0(SB), DI            //DI = g0的地址,DI = &g0,g0是个全局变量,定义在/src/runtime/proc.go文件中
    LEAQ    (-64*1024+104)(SP), BX      //BX = SP -64*1024 + 104, BX = SP - 63KB
    MOVQ    BX, g_stackguard0(DI)       //g0.stackguard0 = BX,用于检查栈溢出
    MOVQ    BX, g_stackguard1(DI)       //g0.stackguard1 = BX,和g0.stackguard0一样
    MOVQ    BX, (g_stack+stack_lo)(DI)  //g0.stack.lo = BX
    MOVQ    SP, (g_stack+stack_hi)(DI)  //g0.stack.hi = SP,g0.stack,g0的执行堆栈

检查处理器

  • 获取CPU信息检查是不是intel处理器
  • 将CPU的相关信息保存到全局变量
    MOVL    $0, AX     //COUID参数
    CPUID               //返回CPU的信息,如果获取到CPU信息,AX大于0
    CMPL    AX, $0     //比较
    JE  nocpuinfo       //获取不到CPU信息,跳转到nocpuinfo

    //获取到CPU信息,判断是不是intel处理器,如果是BX = "Genu",DX = "ineI",CX = "ntel"
    CMPL    BX, $0x756E6547  // "Genu"
    JNE notintel
    CMPL    DX, $0x49656E69  // "ineI"
    JNE notintel
    CMPL    CX, $0x6C65746E  // "ntel"
    JNE notintel
    MOVB    $1, runtime·isIntel(SB)        //标记是intel,全局变量,同g0

notintel:
    // Load EAX=1 cpuid flags
    MOVL    $1, AX     //获取CPU功能的信息
    CPUID
    MOVL    AX, runtime·processorVersionInfo(SB)//保存到processorVersionInfo变量

nocpuinfo:
    // if there is an _cgo_init, call it.
    MOVQ    _cgo_init(SB), AX       //检查_cgo_init是否初始化
    TESTQ   AX, AX      //判断AX是否等于0
    JZ  needtls         //等于跳转到needtls
    // arg 1: g0, already in DI
    MOVQ    $setg_gcc<>(SB), SI // arg 2: setg_gcc
    ……  //nocpuinfo的其他信息省略掉,直接看needtls

设置TLS(本地线程存储)

  • 将TLS关联到m0(全局变量)的tls属性中
  • 检查设置是否有误
needtls:
    ……  //ifdef条件都不满足,省略掉这部分代码
    LEAQ    runtime·m0+m_tls(SB), DI    //DI = &m0.tls,m0全局变量
    CALL    runtime·settls(SB)      //设置tls为&m0.tls[0]

    // store through it, to make sure it works
    get_tls(BX)     //BX = TLS
    MOVQ    $0x123, g(BX)  //m0.tls[0] = 0x123
    MOVQ    runtime·m0+m_tls(SB), AX    //AX = m0.tls[0]
    CMPQ    AX, $0x123
    JEQ 2(PC)       //相等,跳过runtime·abort(SB)
    CALL    runtime·abort(SB)

m0和g0的相互绑定

  • 将当前工作的协程g0设置到TLS中
  • 将g0和m0相互绑定
ok:
    get_tls(BX)
    LEAQ    runtime·g0(SB), CX      //CX = &runtime.g0
    MOVQ    CX, g(BX)           //runtime.m0.tls[0] = &runtime.g0
    LEAQ    runtime·m0(SB), AX  //AX = &runtime.m0

    // save m->g0 = g0
    MOVQ    CX, m_g0(AX)    //runtime.m0.g0 = runtime.g0
    // save m0 to g0->m
    MOVQ    AX, g_m(CX)     //runtime.g0.m = runtime.m0

    CLD             

初始化操作

  • 初始化命令行参数相关数据
  • 初始化操作系统相关数据
  • 初始化调度器相关数据
    ……  //一堆ifdef条件,不满足,省略掉
    CALL    runtime·check(SB)       //检查各种基本函数调用有没有问题,unsafe.sizeOf,atomic操作等

    MOVL    24(SP), AX      // AX = argc,在第一步操作已经在参数保存在栈位置
    MOVL    AX, 0(SP)       // SP = argc
    MOVQ    32(SP), AX      // BX = argv
    MOVQ    AX, 8(SP)       // SP + 8 = argv
    CALL    runtime·args(SB)        //初始化命令行参数相关数据
    CALL    runtime·osinit(SB)      //初始化操作系统相关数据
    CALL    runtime·schedinit(SB)   //初始化调度器相关数据

创建goroutine调用runtime.main函数

  • runtime.main函数加入到调度队列中
  • 创建一个内核线程
    // create a new goroutine to start program
    MOVQ    $runtime·mainPC(SB), AX        // 这里就是我们runtime.main函数的调用地址,这个runtime.main函数会调用我们写的main函数,下面给出是如何关联的
    PUSHQ   AX          //将AX压入到栈中
    CALL    runtime·newproc(SB)     //调用newproc函数,创建一个gorountine,绑定到GMP中的p中,我们在代码中执行 go func()开启一个协程,调用的就是这个函数,可以理解为执行go runtime.main(),这里加入的协程执行队列,还没执行
    POPQ    AX          //AX出栈

    // start this M
    CALL    runtime·mstart(SB)      //m开始执行协程任务

    CALL    runtime·abort(SB)   //这里也就不会执行 mstart should never return
    RET

mstart函数

TEXT runtime·mstart(SB),NOSPLIT|TOPFRAME,$0
    CALL    runtime·mstart0(SB) //调用mstart0函数
    RET // not reached

mstart0函数

设置堆栈保护边界

func mstart0() {
    _g_ := getg()   //从TLS获取g,这里是g0

    osStack := _g_.stack.lo == 0//在第1步已经设置了g0.stack.lo的值了,这里为false
    if osStack {
        ……  //不执行省略掉
    }
    _g_.stackguard0 = _g_.stack.lo + _StackGuard    //留出一定空间保护防止栈溢出
    _g_.stackguard1 = _g_.stackguard0   //留出一定空间保护防止栈溢出
    mstart1()   //不会退出

    if mStackIsSystemAllocated() {
        osStack = true
    }
    mexit(osStack)
}

mstart1函数

保存g0的父调度信息

func mstart1() {
    _g_ := getg()   //g0

    if _g_ != _g_.m.g0 {
        throw("bad runtime·mstart")
    }

    _g_.sched.g = guintptr(unsafe.Pointer(_g_)) //g0.sched.g = g0
    _g_.sched.pc = getcallerpc()    //调用者PC值
    _g_.sched.sp = getcallersp()    //调用者SP值

    asminit()   //空实现
    minit() //初始化信号相关操作,并设置线程id

    if _g_.m == &m0 {
        mstartm0()  //初始化信号
    }

    if fn := _g_.m.mstartfn; fn != nil {    //对于启动流程来说,不执行
        fn()
    }

    if _g_.m != &m0 {   //不满足条件,不执行
        acquirep(_g_.m.nextp.ptr())
        _g_.m.nextp = 0
    }
    schedule()      //调度
}

schedule调度函数

寻找一个可执行的goroutine执行

func schedule() {
    _g_ := getg() //获取当前g

    if _g_.m.locks != 0 {   //不满足条件
        throw("schedule: holding locks")
    }

    if _g_.m.lockedg != 0 { //不满足条件
        stoplockedm()
        execute(_g_.m.lockedg.ptr(), false) // Never returns.
    }

    if _g_.m.incgo {    //不满足条件
        throw("schedule: in cgo")
    }

top:
    pp := _g_.m.p.ptr() //GMP模型中的p
    pp.preempt = false //当前p抢占标记为false

    if _g_.m.spinning && (pp.runnext != 0 || pp.runqhead != pp.runqtail) {
        throw("schedule: spinning with local work")
    }

    gp, inheritTime, tryWakeP := findRunnable() //找到一个可运行的g,这里只有一个g,那就是runtime.main函数(第6步,CALL  runtime·newproc(SB)设置的),先不展开

    if _g_.m.spinning { //启动流程不会执行
        resetspinning()
    }

    if sched.disable.user && !schedEnabled(gp) {    //启动流程不会执行
        …… //不执行省略掉
    }

    if tryWakeP {   //启动流程不会执行
        wakep()
    }

    if gp.lockedm != 0 {    //启动流程不会执行
        startlockedm(gp)
        goto top
    }

    execute(gp, inheritTime)    //运行g
}

execute函数

将goroutine与m0绑定,g在m0上运行

func execute(gp *g, inheritTime bool) {
    //gp:执行runtime.main函数的g,我们描述为 main_g
    _g_ := getg()        //_g_:g0

    if goroutineProfile.active {    //false,不管
        tryRecordGoroutineProfile(gp, osyield)
    }

    //将main_g与m相互绑定关联
    _g_.m.curg = gp //设置当前运行g为main_g
    gp.m = _g_.m    //设置main_g与m绑定
    casgstatus(gp, _Grunnable, _Grunning)   //将main_g的状态从可运行的设置为运行中
    gp.waitsince = 0        //main_g的阻塞时间
    gp.preempt = false      //标记为可抢占
    gp.stackguard0 = gp.stack.lo + _StackGuard //设置栈溢出保护
    if !inheritTime {   //inheritTime为true
        _g_.m.p.ptr().schedtick++ //没有继承的时间,p的调度次数加1
    }

    //确认cpu分析器是否需要打开
    hz := sched.profilehz
    if _g_.m.profilehz != hz {      //不管
        setThreadCPUProfiler(hz)
    }

    if trace.enabled {  //不管
        // GoSysExit has to happen when we have a P, but before GoStart.
        // So we emit it here.
        if gp.syscallsp != 0 && gp.sysblocktraced {
            traceGoSysExit(gp.sysexitticks)
        }
        traceGoStart()
    }

    gogo(&gp.sched) //执行runtime.main函数
}

gogo函数

获取gorountine的上下文信息,执行runtime.main函数

// func gogo(buf *gobuf)    //buf = &gp.sched
TEXT runtime·gogo(SB), NOSPLIT, $0-8
   MOVQ   buf+0(FP), BX     // BX = buf, FP:是一个寄存器,它指向当前函数的栈帧
   MOVQ   gobuf_g(BX), DX     // DX = buf.g
   MOVQ   0(DX), CX     //CX = DX = buf.g,make sure g != nil
   JMP    gogo<>(SB)    //调到gogo<>执行,也就是下面的函数

TEXT gogo<>(SB), NOSPLIT, $0
    get_tls(CX)             // CX = tls
    MOVQ    DX, g(CX)       // 将当前运行的g,保存在tls中,getg()才能获取到当前运行的g
    MOVQ    DX, R14     //  R14 = g    set the g register
    MOVQ    gobuf_sp(BX), SP    //设置g的栈顶, SP = buf.sp restore SP
    MOVQ    gobuf_ret(BX), AX   // AX = buf.ret
    MOVQ    gobuf_ctxt(BX), DX  // DX = buf.ctxt
    MOVQ    gobuf_bp(BX), BP    //设置g的栈底 BP = buf.bp
    MOVQ    $0, gobuf_sp(BX)   // 清空gobuf大部分信息 clear to help garbage collector
    MOVQ    $0, gobuf_ret(BX)
    MOVQ    $0, gobuf_ctxt(BX)
    MOVQ    $0, gobuf_bp(BX)
    MOVQ    gobuf_pc(BX), BX    // BX = buf.pc,buf.pc就是runtime.main函数的地址,下面runtime.newproc的执行简单分析节,分析这个为什么是runtime.main函数
    JMP BX                      // 执行runtime.main函数

runtime.newproc的执行简单分析

这里只先简单分析一下runtime.main是如何被保存到goroutine上的

  1. newproc函数

    func newproc(fn *funcval) {
       gp := getg()        //获取当前g
       pc := getcallerpc() //获取caller pc
       systemstack(func() {    //systemstack:切换到系统(线程)栈,如果是g0的话,就在g0的栈上执行,不切换,简单了解一下,可以不关注
           newg := newproc1(fn, gp, pc)
           _p_ := getg().m.p.ptr()
           runqput(_p_, newg, true)    //将新开的goroutine保存到本地的p,p是GMP模型中的p
           if mainStarted {    //这个时候还是false,runtime.main函数执行时,会置为true
               wakep()
           }
       })
    }
  2. newproc1函数

    func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
       _g_ := getg()       //g0

       if fn == nil {
           fatal("go of nil func value")
       }
       acquirem() // 禁止抢占,m上不会再切换其他g进来

       _p_ := _g_.m.p.ptr() //拿到p
       newg := gfget(_p_)   //拿一个dead G,当前协程执行结束之后,goroutine不会马上回收,而是将goroutine的状态设置为dead G,复用goroutine
       if newg == nil {     //拿不到G,就分配一个
           newg = malg(_StackMin)  //创建一个g对象
           casgstatus(newg, _Gidle, _Gdead) //置为Dead状态
           allgadd(newg)                    // 加到全局g对象中
       }
       if newg.stack.hi == 0 {
           throw("newproc1: newg missing stack")
       }

       if readgstatus(newg) != _Gdead {
           throw("newproc1: new g is not Gdead")
       }

       totalSize := uintptr(4*goarch.PtrSize + sys.MinFrameSize) //额外空间,读取时稍微超出
       totalSize = alignUp(totalSize, sys.StackAlign)            //调整大小
       sp := newg.stack.hi - totalSize                           //goroutine的栈顶
       spArg := sp
       if usesLR {     //false
           // caller's LR
           *(*uintptr)(unsafe.Pointer(sp)) = 0
           prepGoExitFrame(sp)
           spArg += sys.MinFrameSize
       }

       //清楚上一个goroutine执行的上下文信息
       memclrNoHeapPointers(unsafe.Pointer(&newg.sched), unsafe.Sizeof(newg.sched))
       //调度相关数据设置
       newg.sched.sp = sp       //栈顶
       newg.stktopsp = sp      //检查traceback,不管
       newg.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum //函数的退出地址
       newg.sched.g = guintptr(unsafe.Pointer(newg))          //g本身
       gostartcallfn(&newg.sched, fn)      //设置newg.sched.buf.pc = fn.fn,fn.fn就是runtime.main函数,还有newg.sched.buf的其他信息
       newg.gopc = callerpc    //设置goroutine的调用者pc
       newg.ancestors = saveAncestors(callergp)    //设置goroutine的祖先
       newg.startpc = fn.fn    //设置goroutine的startpc
       if isSystemGoroutine(newg, false) {     //false
           atomic.Xadd(&sched.ngsys, +1)
       } else {
           if _g_.m.curg != nil {
               newg.labels = _g_.m.curg.labels //不关注,设置goroutines的labels
           }
           if goroutineProfile.active {    //false
               newg.goroutineProfiled.Store(goroutineProfileSatisfied)
           }
       }
       newg.trackingSeq = uint8(fastrand())    //初始化一个随机数
       if newg.trackingSeq%gTrackingPeriod == 0 {  //false
           newg.tracking = true
       }
       casgstatus(newg, _Gdead, _Grunnable)    //将goroutine设置为可运行的
       gcController.addScannableStack(_p_, int64(newg.stack.hi-newg.stack.lo)) //GC相关,不管

       if _p_.goidcache == _p_.goidcacheend {  //这里就是给goroutine设置一个编号,在同一个p下,从1开始,2、3、4 ...
           _p_.goidcache = atomic.Xadd64(&sched.goidgen, _GoidCacheBatch)
           _p_.goidcache -= _GoidCacheBatch - 1
           _p_.goidcacheend = _p_.goidcache + _GoidCacheBatch
       }
       newg.goid = int64(_p_.goidcache)设置进去
       _p_.goidcache++
       if raceenabled {
           ……  //不会执行,省略掉
       }
       if trace.enabled {      //忽略
           traceGoCreate(newg, newg.startpc)
       }
       releasem(_g_.m)     //释放,运行抢占

       return newg     //返回g对象
    }
  3. gostartcallfn函数(将runtime.main执行地址设置到g.sched.buf.pc上)

    func gostartcallfn(gobuf *gobuf, fv *funcval) {
       var fn unsafe.Pointer
       if fv != nil {
           fn = unsafe.Pointer(fv.fn)      //取出runtime.main
       } else {
           fn = unsafe.Pointer(abi.FuncPCABIInternal(nilfunc))
       }
       gostartcall(gobuf, fn, unsafe.Pointer(fv))      //在这个函数上设置
    }

    func gostartcall(buf *gobuf, fn, ctxt unsafe.Pointer) {
       sp := buf.sp
       sp -= goarch.PtrSize        //扩展8字节,用来保存buf.pc
       *(*uintptr)(unsafe.Pointer(sp)) = buf.pc        //栈上保存的buf.pc的值
       buf.sp = sp     //重新设置buf.sp
       buf.pc = uintptr(fn)        //设置buf.pc = runtime.main,也是将runtime.main执行地址设置到g.sched.buf.pc上
       buf.ctxt = ctxt
    }

    g.sched.buf.pc = runtime.main,所以gogo<>函数最终会调到runtime.main函数执行

runtime.main函数

执行init函数,执行main函数

func main() {
    g := getg() //获取当前g

    g.m.g0.racectx = 0  //g0只在main goroutine使用

    if goarch.PtrSize == 8 {    //判断是不是64位机器
        maxstacksize = 1000000000   //最大栈1GB
    } else {
        maxstacksize = 250000000
    }

    maxstackceiling = 2 * maxstacksize  //2倍最大栈

    mainStarted = true

    if GOARCH != "wasm" { // no threads on wasm yet, so no sysmon
        systemstack(func() {
            newm(sysmon, nil, -1) //新开一个线程运行sysmon
        })
    }

    lockOSThread()  //将main goroutine锁定到这个主操作系统线程上

    if g.m != &m0 {
        throw("runtime.main not on m0")
    }

    runtimeInitTime = nanotime()    //获取当前时间戳
    if runtimeInitTime == 0 {   //不满足
        throw("nanotime returning zero")
    }

    if debug.inittrace != 0 {   //不满足
        inittrace.id = getg().goid
        inittrace.active = true
    }

    //运行runtime包下的init函数
    doInit(&runtime_inittask) // Must be before defer.

    needUnlock := true
    defer func() {
        if needUnlock {
            unlockOSThread()
        }
    }()

    gcenable()      //启用GC

    main_init_done = make(chan bool)
    if iscgo {      //不开启cgo模式
        ……  //不执行省略掉
    }

    //执行main包下的初始化
    doInit(&main_inittask)

    inittrace.active = false    //不管,关闭inittrace,避免在malloc和newproc中收集统计信息的开销

    close(main_init_done)   //不管

    needUnlock = false  //不需要defer函数来解锁
    unlockOSThread()

    if isarchive || islibrary { //不满足
        return
    }
    fn := main_main // 这个就是我们的main函数,后面将是如何关联的
    fn()    //执行main函数,到这里,我们的main函数就被执行了
    if raceenabled {
        racefini()
    }

    //main函数退出后,程序就该关闭了
    //defer panic相关
    if atomic.Load(&runningPanicDefers) != 0 {
        // Running deferred functions should not take long.
        for c := 0; c < 1000; c++ {
            if atomic.Load(&runningPanicDefers) == 0 {
                break
            }
            Gosched()
        }
    }
    if atomic.Load(&panicking) != 0 {
        gopark(nil, nil, waitReasonPanicWait, traceEvGoStop, 1)
    }

    exit(0)     //退出程序
    for {
        var x *int32
        *x = 0
    }
}

至此,main函数的执行流程就结束了

main函数是如何关联上的

runtime·mainPC与runtime·main关联

在创建goroutine调用main函数一节中,我们看到有这样一条指令MOVQ $runtime·mainPC(SB), AX,我们当时提到这个$runtime·mainPC是runtime.main函数的调用地址,这个变量定义在/src/runtime/asm_amd64.s文件中,可以看到有两条汇编指令

// mainPC is a function value for runtime.main, to be passed to newproc.
// The reference to runtime.main is made via ABIInternal, since the
// actual function (not the ABI0 wrapper) is needed by newproc.
DATA    runtime·mainPC+0(SB)/8,$runtime·main<ABIInternal>(SB)
GLOBL    runtime·mainPC(SB),RODATA,$8

GlOBL声明runtime·mainPC是一个全局变量,所以可以直接被引用

DATA 定义变量runtime·mainPC是一个只读数据变量,大小为8字节,变量值是runtime·main(SB)的函数地址

main_main与main.main关联

在runtime.main函数一节中,我们分析了这一段代码

func main() {
    ……
    fn := main_main
    fn()    //main函数的调用
    ……
}

在main函数上方,有这样一个函数声明

//go:linkname main_main main.main
func main_main()

起作用的就是这个注释//go:linkname(官网对于go:linkname的说明),表示main_main函数由main.main来实现

main.main:第一个main,表示的是package,也就是包名,第二个main,就是我们的函数名

所以main_main就是我们自己写的main函数

总结

最后,总结一下启动程序到执行main函数,有哪些步骤

  1. 分配栈空间,保存命令行参数到栈中,初始化g0的栈信息
  2. 检查处理器是不是intel
  3. 设置本地线程tls,值为&m0.tls[0]
  4. 将m0和g0相互绑定,g0在m0上运行
  5. 创建一个执行runtime.main的goroutine到p的本地队列中
  6. 设置堆栈保护边界
  7. 保存g0的父调度信息,初始化信号相关操作,调度
  8. 取出执行runtime.main的goroutine,并运行gorountine
  9. 执行到runtime.main函数,执行init函数,然后再调用我们编写main函数

参考

CPUID指令

系统调用arch_prctl

标签:runtime,函数,g0,流程,newg,_.,go,main
From: https://www.cnblogs.com/feng-learn/p/17996962

相关文章

  • 从零搭建Go语言Web服务器
    从零搭建Go语言Web服务器原创 Go先锋 Go先锋 2024-01-3011:19 发表于广东 听全文Go先锋读完需要9分钟速读仅需3分钟  一、Go语言的优势1.执行效率高Go语言以其出色的执行效率而闻名,这得益于其静态类型和编译型的特性。通过直接编译成机器码,Go程序......
  • 漫画图解 Go 并发编程之:Channel
    当谈到并发时,许多编程语言都采用共享内存/状态模型。然而,Go通过实现CommunicatingSequentialProcesses(CSP)而与众不同。在CSP中,程序由不共享状态的并行处理器组成;相反,他们使用Channel来沟通和同步他们的行动。因此,对于有兴趣采用Go的开发人员来说,理解Channel的工作原理......
  • 使用Golang实现ping检测主机在线的功能
    使用"github.com/go-ping/ping"这个第三方库可以非常简单的实现ping功能packagemainimport("fmt""os""time""github.com/go-ping/ping")funcCheckHostOnline(ipaddrstring)bool{pinger,err:=ping.N......
  • 漫画图解 Go 并发编程之:Channel
    当谈到并发时,许多编程语言都采用共享内存/状态模型。然而,Go通过实现CommunicatingSequentialProcesses(CSP)而与众不同。在CSP中,程序由不共享状态的并行处理器组成;相反,他们使用Channel来沟通和同步他们的行动。因此,对于有兴趣采用Go的开发人员来说,理解Channel的工作原理......
  • 龙年红包封面粉丝专属限量款红包封面-AI定制全流程
    快过年了,微信红包封面也开始卷了起来。今年在AI辅助下,几乎每个人都能设计自己的专属红包封面了。有了AI绘图,登录微信红包封面开放平台,就可以开始定制。需要提醒的是:无法以个人身份定制的,微信要求,你必须有个100粉的公众号或者视频号,才可以定制。封面定制操作你有了资格后,认证......
  • Prism:打造WPF项目的MVVM之选,简化开发流程、提高可维护性
     概述:探索WPF开发新境界,借助PrismMVVM库,实现模块化、可维护的项目。强大的命令系统、松耦合通信、内置导航,让您的开发更高效、更流畅在WPF开发中,一个优秀的MVVM库是Prism。以下是Prism的优点以及基本应用示例:优点:模块化设计: Prism支持模块化开发,使项目更易维护和扩展。......
  • GoLand 2023:专注性能,提升Go开发者的工作效率 mac/win版
    JetBrainsGoLand2023是一款专为Go语言开发者设计的集成开发环境(IDE)。它提供了全面的工具和服务,旨在提高Go语言开发的效率和生产力。→→↓↓载GoLand2023mac/win版 GoLand2023具有许多先进的特性,其中最突出的特点是其对Go语言的深度支持。它提供了智能代码编辑器,能够......
  • 无涯教程-Django - Apache配置
    到目前为止,在无涯教程的示例中,已经使用了DjangodevWeb服务器,但是此服务器仅用于测试,不适合生产环境。一旦投入生产,您就需要一个真实的服务器,如Apache,Nginx等,在本章中讨论Apache。通过Apache为Django应用提供服务是通过使用mod_wsgi完成的。因此,第一件事是确保您已安装Apache和......
  • 【Django开发】前后端分离美多商城项目:项目准备和搭建(附代码,文档)
    本系列文章md笔记(已分享)主要讨论django商城项目开发相关知识。本项目利用Django框架开发一套前后端不分离的商城项目(4.0版本)含代码和文档。功能包括前后端不分离,方便SEO。采用Django+Jinja2模板引擎+Vue.js实现前后端逻辑,Nginx服务器(反向代理)Nginx服务器(静态首页、商品详情页......
  • 【19.0】MySQL进阶知识之流程控制
    【零】各种语言中的流程控制语句【1】Pythonif条件:子代码elif条件:子代码else:子代码【2】JavaScriptif(条件){子代码}elseif(条件){子代码}else{子代码}【3】MySQLif语句if条件then子代码elseif条件then子代码else......