其他

  • gcWork

每个 P 上都有一个 gcw 用来管理灰色对象(get 和 put),gcw 的结构就是 gcWork。gcWork 中的核心是 wbuf1 和 wbuf2,里面存储就是灰色对象,或者说是 work(下面就全部统一叫做 work)。

  1. type p struct {
  2. ...
  3. gcw gcWork
  4. }
  5. type gcWork struct {
  6. // wbuf1 and wbuf2 are the primary and secondary work buffers.
  7. wbuf1, wbuf2 wbufptr
  8. // Bytes marked (blackened) on this gcWork. This is aggregated
  9. // into work.bytesMarked by dispose.
  10. bytesMarked uint64
  11. // Scan work performed on this gcWork. This is aggregated into
  12. // gcController by dispose and may also be flushed by callers.
  13. scanWork int64
  14. }

既然每个 P 上有一个 work buffer,那么是不是还有一个全局的 work list 呢?是的。通过在每个 P 上绑定一个 work buffer 的好处和 cache 一样,不需要加锁。

  1. var work struct {
  2. full uint64 // lock-free list of full blocks workbuf
  3. empty uint64 // lock-free list of empty blocks workbuf
  4. pad0 [sys.CacheLineSize]uint8 // prevents false-sharing between full/empty and nproc/nwait
  5. ...
  6. }

那么为什么使用两个 work buffer (wbuf1 和 wbuf2)呢?例如我现在要 get 一个 work 出来,先从 wbuf1 中取,wbuf1 为空的话则与 wbuf2 swap 再 get。在其他时间将 work buffer 中的 full 或者 empty buffer 移到 global 的 work 中。 这样的好处在于,在 get 的时候去全局的 work 里面取(多个 goroutine 去取会有竞争)。这里有趣的是 global 的 work list 是 lock-free 的,通过原子操作 cas 等实现。下面列举几个函数看一下 gcWrok。

  • 初始化

    1. func (w *gcWork) init() {
    2. w.wbuf1 = wbufptrOf(getempty())
    3. wbuf2 := trygetfull()
    4. if wbuf2 == nil {
    5. wbuf2 = getempty()
    6. }
    7. w.wbuf2 = wbufptrOf(wbuf2)
    8. }
  • put

  1. // put enqueues a pointer for the garbage collector to trace.
  2. // obj must point to the beginning of a heap object or an oblet.
  3. func (w *gcWork) put(obj uintptr) {
  4. wbuf := w.wbuf1.ptr()
  5. if wbuf == nil {
  6. w.init()
  7. wbuf = w.wbuf1.ptr()
  8. // wbuf is empty at this point.
  9. } else if wbuf.nobj == len(wbuf.obj) {
  10. w.wbuf1, w.wbuf2 = w.wbuf2, w.wbuf1
  11. wbuf = w.wbuf1.ptr()
  12. if wbuf.nobj == len(wbuf.obj) {
  13. putfull(wbuf)
  14. wbuf = getempty()
  15. w.wbuf1 = wbufptrOf(wbuf)
  16. flushed = true
  17. }
  18. }
  19. wbuf.obj[wbuf.nobj] = obj
  20. wbuf.nobj++
  21. }
  • get
  1. // get dequeues a pointer for the garbage collector to trace, blocking
  2. // if necessary to ensure all pointers from all queues and caches have
  3. // been retrieved. get returns 0 if there are no pointers remaining.
  4. //go:nowritebarrier
  5. func (w *gcWork) get() uintptr {
  6. wbuf := w.wbuf1.ptr()
  7. if wbuf == nil {
  8. w.init()
  9. wbuf = w.wbuf1.ptr()
  10. // wbuf is empty at this point.
  11. }
  12. if wbuf.nobj == 0 {
  13. w.wbuf1, w.wbuf2 = w.wbuf2, w.wbuf1
  14. wbuf = w.wbuf1.ptr()
  15. if wbuf.nobj == 0 {
  16. owbuf := wbuf
  17. wbuf = getfull()
  18. if wbuf == nil {
  19. return 0
  20. }
  21. putempty(owbuf)
  22. w.wbuf1 = wbufptrOf(wbuf)
  23. }
  24. }
  25. // TODO: This might be a good place to add prefetch code
  26. wbuf.nobj--
  27. return wbuf.obj[wbuf.nobj]
  28. }