Avatar
😀

Organizations

  • 结构分析

    type Pool struct {
    	noCopy noCopy
    
    	local     unsafe.Pointer // P 本地池,固定尺寸,实际结构 [P]poolLocal,类似 void* 并附加长度构成了一个数组
    	localSize uintptr        // size of the local array
    
    	victim     unsafe.Pointer // local from previous cycle
    	victimSize uintptr        // size of victims array
    
    	New func() any
    }
    
    type poolChain struct{
        head *poolChainElt
    
        tail *poolChainElt
    }
    
    type poolChainElt struct{ // 一个双向链表
        poolDequeue
    
        next,prev *poolChainElt
    }
    
    type poolDequeue struct{
        headtail uint64
    
        vals []eface
    }
    
    type eface struct{  // 数据的真实内存分配,包括一个类型描述和实际数据
        typ,val unsafe.Pointer
    }
    
    type poolLocalInternal struct{
        private any // p的私有缓冲区
    
        shared poolChain // 公共缓冲区
    }
    
    type poolLocal struct{
        poolLocalInternal
    
        pad [128-unsafe.Sizeof(poolLocalInternal{})%128]byte  // 应该是补位,可以确保刚好占满一个 cache line 加速访问
    }
    

    申请释放

    
    func (p *Pool) pin() (*poolLocal, int) {
    	pid := runtime_procPin() // 将当前G和P绑定,并返回P的id
    	s := runtime_LoadAcquintptr(&p.localSize) // load-acquire
    	l := p.local                              // load-consume
    	if uintptr(pid) < s {  // 主要是P的数量可能会变动 重新找一个
    		return indexLocal(l, pid), pid
    	}
    	return p.pinSlow()
    }
    
    func (p *Pool) pinSlow() (*poolLocal, int) {
    	runtime_procUnpin()  // 禁止 P 抢占,否则当前G会被放回本地或者全局队列,当时之后G不一定在现在这个P上
    	allPoolsMu.Lock()
    	defer allPoolsMu.Unlock()
    	pid := runtime_procPin()
    	s := p.localSize
    	l := p.local
    	if uintptr(pid) < s { // double check
    		return indexLocal(l, pid), pid
    	}
    	if p.local == nil {  // 如果本地队列为空,那么此时Pool没被初始化,加入全局池引用
    		allPools = append(allPools, p)
    	}
    
    	size := runtime.GOMAXPROCS(0) // 查看现在P的个数
    	local := make([]poolLocal, size) // 为这个Pool分配跟P数量相同的本地池
    	atomic.StorePointer(&p.local, unsafe.Pointer(&local[0])) // store-release
    	runtime_StoreReluintptr(&p.localSize, uintptr(size))     // store-release
    	return &local[pid], pid // 返回当前和P绑定的本地池
    }
    
    func (p *Pool) Get() any {
    	if race.Enabled {
    		race.Disable()
    	}
    	l, pid := p.pin() // 先找本地池
    	x := l.private
    	l.private = nil
    	if x == nil { // 如果没有,那么从全局池找
    		// Try to pop the head of the local shard. We prefer
    		// the head over the tail for temporal locality of
    		// reuse.
    		x, _ = l.shared.popHead()
    		if x == nil {
    			x = p.getSlow(pid)
    		}
    	}
    	runtime_procUnpin() //释放P,让其可以被抢占
    	if race.Enabled {
    		race.Enable()
    		if x != nil {
    			race.Acquire(poolRaceAddr(x))
    		}
    	}
    	if x == nil && p.New != nil {
    		x = p.New()
    	}
    	return x
    }
    
    
    // Put adds x to the pool.
    func (p *Pool) Put(x any) {
    	if x == nil {
    		return
    	}
    	if race.Enabled {
    		if fastrandn(4) == 0 {
    			// Randomly drop x on floor.
    			return
    		}
    		race.ReleaseMerge(poolRaceAddr(x))
    		race.Disable()
    	}
    	l, _ := p.pin() // 老规矩,先禁止P被抢占
    	if l.private == nil { // 本地没有 则先放入本地
    		l.private = x
    	} else {
    		l.shared.pushHead(x) // 否则放入全局
    	}
    	runtime_procUnpin()
    	if race.Enabled {
    		race.Enable()
    	}
    }
    

    GC

    其实很好理解,正好是一次二级缓冲模型,第一次gc会将local放入 victim,第二gc victim不为空才会真正清理,local不会参与gc

    sync.pool Created 15 Apr 2022
  • Cgo

    cgo 一种go与c交互的技术

    开启cgo

    要求系统安装C/C++工具链,macos和linux(gcc 自带),windows(mingw),并确保环境变量CGO_ENAVBLE=on,最后单个源码需要导入 import "C"

    cgo类型映射

    C type Cgo type Go type
    char C.char byte
    signed char C.schar int8
    unsigned char C.uchar uint8
    short C.short int16
    unsigned short C.ushort uint16
    int C.int int32
    unsigned int C.uint uint32
    long C.long int32
    unsigned long C.ulong uint32
    long long int C.longlong int64
    unsigned long long int C.ulonglong uint64
    float C.float float32
    double C.double double
    size_t C.size_t uint

    函数指针

    go引用c的函数指针比较特别

    1. 官方给出的Example

    Created 6 Apr 2022
  • 如何启用GC跟踪

    GODEBUG=gctrace=1 go run *.go

    其中 gctrace=1 表示只针对这个进程进行GC追踪

    标记流程

    go采用三色标记法,主要是为了提高并发度,这样扫描过程可以拆分为多个阶段,而不用一次扫描全部

    • 黑 根节点扫描完毕,子节点也扫描完毕

    • 灰 根节点扫描完毕,子节点未扫描

    • 白 未扫描

    扫描是从 .bss .data goroutine栈开始扫描,最终遍历整个堆上的对象树

    标记 mark

    标记过程是一个广度优先的遍历过程,扫描节点,将节点的子节点推送到任务队列中,然后递归扫描子叶节点,直到所有工作队列被排空

    mark阶段会将白色对象标记,并推入队列中变为灰色

    memory barrier

    保障了代码描述中对内存的操作顺序,即不会在编译期被编译器进行调整,也不会在运行时被CPU的乱序执行所打乱

    write barrier

    在应用进入 GC 标记阶段前的 stw 阶段,会将全局变量 runtime.writeBarrier.enabled 修改为 true,这时所有的堆上指针修改操作在修改之前便会额外调用 runtime.gcWriteBarrier

    由于GC和Go主程序并发执行,所以必须要在扫描时监控内存可能出现的状态改变,所以需要写屏障,所以需要暂停GO主程序(STW)

    hybrid wirte barrier (after go1.8)

    改方式的基本思想是:对正在被覆盖的对象进行着色,且如果当时栈未扫描完成,则同样对指针进行着色

    GC流程

    程序启动会为每个P分配一个 mark worker 来标记内存,负责为进入STW做前期工作

    • 起初认为所有 object 都被认定为白色
    • 但栈,堆和全局变量的object被标记为灰色

    GC会将灰色object标记为黑色,将灰色object所包含的所有指针所指向的地址都标记为灰色,递归这两个步骤,最终对象非黑即白,其中白色object即未被引用且可以被回收,如果object标记为no scan,则递归结束,标记为黑色

    todo https://blog.csdn.net/asd1126163471/article/details/124113816

    GC Created 26 Feb 2022
  • Golang 默认指针是类型安全的,但它有很多限制。Golang 还有非类型安全的指针,这就是 unsafe 包提供的 unsafe.Pointer。在某些情况下,它会使代码更高效,当然,也更危险。unsafe 包用于 Go 编译器,在编译阶段使用。从名字就可以看出来,它是不安全的,官方并不建议使用。Go 语言类型系统是为了安全和效率设计的,有时,安全会导致效率低下。unsafe 包绕过了 Go 的类型系统,达到直接操作内存的目的,使用它有一定的风险性。但是在某些场景下,使用 unsafe 包提供的函数会提升代码的效率,Go 源码中也是大量使用 unsafe 包。

    unsafe

    //定义
    type ArbitraryType int
    
    type Pointer *ArbitraryType 
    
    //函数
    func Sizeof(x AribitraryType) uintptr{}
    
    func Offsetof(x AribitraryType) uintptr{}
    
    func Alignof(x AribitraryType) uintptr{}
    

    分析

    • Pointer : 指向任意类型,类似于 C 中的 void*

    • Sizeof : 返回所传类型的大小,指针只返回指针的本身(x64 8byte x86 4byte),而不会返回所指向的内存大小。

    • Offsetof : 返回 struct 成员在内存中的位置,相对于此结构体的头位置,所传参数必须是结构体成员。传入指针,或者结构体本身,会 error

    • Alignof : 返回 M,M 是内存对齐时的倍数。

    • 任意指针都可以和 unsafe.Pointer 相互转换。

    • uintptr 可以和 unsafe.Pointer 相互转换。

    综上,unsafe.Pointer 是不能进行指针运算的,只能先转为 uintptr 计算完再转回 unsafe.Pointer ,还有一点要注意的是, uintptr 并没有指针的语义,意思就是 uintptr 所指向的对象会被 gc。而 unsafe.Pointer 有指针语义,可以保护它所指向的对象在“有用”的时候不会被垃圾回收。

    Created 25 Aug 2021
  • 一个 os 线程会有一个给固定大小的内存块(一般是 2MB),用来存储当前线程中调用或挂起函数的内部变量,固定大小的栈对于复杂和深层次递归是不够的,而 Goroutine 会以一个很小的栈(2KB)开始其生命周期,这个栈会动态伸缩,最大能到达 1GB(32位系统是 250M)

    调度方式

    os 线程由操作系统内核调用,每过一定时间(毫秒),硬件计时器会中断处理器,并调用一个名为 scheduler 的内建函数,这个函数会挂起当前执行的线程并保存内存中它的寄存器内存,然后检查线程列表并决定下一次执行哪个线程,并从内存中恢复该线程的寄存器信息,恢复该线程的线程并执行,这就是上下文切换,增加了 CPU 的运行周期。而 Go 的 runtime 包含了自身的调度器,和 os 线程不同是,Goroutine 属于用户级线程由语言支持,调度由语言支持,所有开销会减少很多(相比于内核上下文切换)。

    goroutine Created 12 May 2019