0%

Go语言内存杂谈

在Go语言中,变量、结构体、成员、函数、接口等第一个字母的大小写决定了其访问权限,大写字母开头表示公共的,可供外部调用;小写字母开头表示私有的,外部不能调用。

一、基础

  1. 内存管理

      Golang运行时的内存分配算法主要源自Google为C语言开发的TCMalloc算法,全称Thread-Caching Malloc。核心思想就是把内存分为多级管理,从而降低锁的粒度。它将可用的堆内存采用二级分配的方式进行管理:每个线程都会自行维护一个独立的内存池,进行内存分配时优先从该内存池中分配,当内存池不足时才会向全局内存池申请,以避免不同线程对全局内存池的频繁竞争。

  • 申请内存
  • 回收内存
  1. goroutine并发访问

      Go内存模型明确指出,一个goroutine如何才能观察到其他goroutine对同一变量的写操作。当多个goroutine并发同时读写同一个数据时必须把并发的存取操作序列化,在Go中保证读写的序列化可以通过channel通信或者其他同步原语实现(例如sync包中的互斥锁、读写锁和sync/atomic中的原子操作)。解决多goroutine下共享数据可见性问题的方法是在访问共享数据时候施加一定的同步措施

  1. happens-before

    • 理解

      • 条件一:如果对于一个变量v的读操作r和写操作w满足下述两个条件,r才允许观察到w:
        • r没有发生在w之前
        • 没有其他写操作发生在w之后和r之前
      • 条件二:为了保证变量v的一个读操作r能够观察到一个特定的写操作w,需要确保w是唯一允许被r观察的写操作。那么如果 r、w都满足以下条件,r就能确保观察到w:
        • w发生在r之前
        • 其他写操作发生在w之前后者r之后
    • Go中happens-before的保证

      • 单线程

      • Init函数

        • 如果包P1中导入了包P2,则P2中的init函数Happens Before 所有P1中的操作
        • main函数Happens After 所有的init函数
      • Goroutine

        • Goroutine的创建Happens Before所有此Goroutine中的操作
        • Goroutine的销毁Happens After所有此Goroutine中的操作
      • Channel

        • 对一个元素的send操作Happens Before对应的receive完成操作——[先发后接]
        • 对channel的close操作Happens Before receive端的收到关闭通知操作——[先关后接 接到零值]
        • 对于无缓冲信道,对一个元素的receive操作Happens Before对应的send完成操作——[先接后发]
        • 对于有缓冲信道,假设Channel的buffer大小为C,那么对第k个元素的receive操作Happens Before第k+C个send完成操作。

          可以看出上一条Unbuffered Channel规则就是这条规则C=0时的特例 [先接后发]

      • Lock

        • Go里面有Mutex和RWMutex两种锁,RWMutex除了支持互斥的Lock/Unlock,还支持共享的RLock/RUnlock。
        • 对于一个Mutex/RWMutex,设n < m,则第n个Unlock操作Happens Before第m个Lock操作。
        • 对于一个RWMutex,存在数值n,RLock操作Happens After 第n个UnLock,其对应的RUnLock Happens Before 第n+1个Lock操作。

          简单理解就是这一次的Lock总是Happens After上一次的Unlock,读写锁的RLock HappensAfter上一次的UnLock,其对应的RUnlock Happens Before 下一次的Lock。

      • Once:sync包的Once为多个goroutine提供了安全的初始化机制。能在多个线程中执行once.Do(f),但只有一个f()会执行,其他调用会一直阻塞直到f()返回。

  2. 内存泄漏

  3. 垃圾回收

    • 标记清除: v1.3

      • 过程
        • stw开始(stop the world),具体是指停止正在运行的goroutines
        • mark:标记可达对象(分出可达对象和不可达对象)
          • 从程序的根节点往下遍历访问,能遍历到的就是可达对象,不能则为不可达的对象
        • sweep:清除不可达对象
        • stw结束
      • 缺点
        • 存在STW,因为在GC阶段需要暂停整个程序,程序可能会出现卡顿影响性能
        • 标记过程需要扫描整个堆栈信息,进一步加长了整个STW的持续时间
        • 清除数据会产生内存碎片(heap碎片),heap碎片是不连续的,后续重用困难
    • 三色标记: v1.5

      • 过程
        • 遍历根对象的第一层可达对象标记为灰色,不可达默认白色
        • 将灰色对象的下一层可达对象标记为灰色,自身标记为黑色
        • 多次重复步骤2,直到灰色对象为0,只剩下白色对象和黑色对象
        • sweep白色对象
      • 缺点
        • 如果GC期间不stw有可能会对象丢失(一个黑色对象在GC期间链接了白色对象,白色对象又没有任何灰色对象可达就会导致对象的丢失)
    • 混合写屏障+三色标记: v1.8

      • 过程
        • STW扫描栈,将可达对象标记为黑色
        • GC期间stack创建的对象都是灰色
        • GC期间在堆添加混合写屏障
        • 继续三色标记过程
      • 优缺点

二、实战

三、参考

  1. 参考一
  2. 参考二
  3. 参考三
  4. 参考四
  5. 参考五
  6. 参考六
  7. 参考七
  8. 参考七