golang sema

88 阅读2分钟
// Semrelease atomically increments *s and notifies a waiting goroutine
// if one is blocked in Semacquire.

func runtime_Semrelease(s *uint32, handoff bool, skipframes int)
func semacquire1(addr *uint32, lifo bool, profile semaProfileFlags, skipframes int) {
   gp := getg()
   if gp != gp.m.curg {
      throw("semacquire not on the G stack")
   }

   // Easy case.
   if cansemacquire(addr) {
      return
   }

   // Harder case:
   // increment waiter count
   // try cansemacquire one more time, return if succeeded
   // enqueue itself as a waiter
   // sleep
   // (waiter descriptor is dequeued by signaler)
   s := acquireSudog()
   root := semroot(addr)
   t0 := int64(0)
   s.releasetime = 0
   s.acquiretime = 0
   s.ticket = 0
   if profile&semaBlockProfile != 0 && blockprofilerate > 0 {
      t0 = cputicks()
      s.releasetime = -1
   }
   if profile&semaMutexProfile != 0 && mutexprofilerate > 0 {
      if t0 == 0 {
         t0 = cputicks()
      }
      s.acquiretime = t0
   }
   for {
      lockWithRank(&root.lock, lockRankRoot)
      // Add ourselves to nwait to disable "easy case" in semrelease.
      atomic.Xadd(&root.nwait, 1)
      // Check cansemacquire to avoid missed wakeup.
      if cansemacquire(addr) {
         atomic.Xadd(&root.nwait, -1)
         unlock(&root.lock)
         break
      }
      // Any semrelease after the cansemacquire knows we're waiting
      // (we set nwait above), so go to sleep.
      root.queue(addr, s, lifo)
      goparkunlock(&root.lock, waitReasonSemacquire, traceEvGoBlockSync, 4+skipframes)
      if s.ticket != 0 || cansemacquire(addr) {
         break
      }
   }
   if s.releasetime > 0 {
      blockevent(s.releasetime-t0, 3+skipframes)
   }
   releaseSudog(s)
}

semaRoot、semtable

A semaRoot holds a balanced tree of sudog with distinct addresses (s.elem). Each of those sudog may in turn point (through s.waitlink) to a list of other sudogs waiting on the same address. The operations on the inner lists of sudogs with the same address are all O(1). The scanning of the top-level semaRoot list is O(log n), where n is the number of distinct addresses with goroutines blocked on them that hash to the given semaRoot. See golang.org/issue/17953 for a program that worked badly before we introduced the second level of list, and test/locklinear.go for a test that exercises this.

type semaRoot struct {
   lock  mutex
   treap *sudog // root of balanced tree of unique waiters.
   nwait uint32 // Number of waiters. Read w/o the lock.
}
// Prime to not correlate with any user patterns.
const semTabSize = 251

var semtable [semTabSize]struct {
   root semaRoot
   pad  [cpu.CacheLinePadSize - unsafe.Sizeof(semaRoot{})]byte
}

data structure : treap

The balanced tree is a treap using ticket as the random heap priority. That is, it is a binary tree ordered according to the elem addresses, but then among the space of possible binary trees respecting those addresses, it is kept balanced on average by maintaining a heap ordering on the ticket: s.ticket <= both s.prev.ticket and s.next.ticket.

插入

// queue adds s to the blocked goroutines in semaRoot.
func (root *semaRoot) queue(addr *uint32, s *sudog, lifo bool) {
   s.g = getg()
   s.elem = unsafe.Pointer(addr)
   s.next = nil
   s.prev = nil

   var last *sudog
   pt := &root.treap
   for t := *pt; t != nil; t = *pt {
      if t.elem == unsafe.Pointer(addr) {
         // Already have addr in list.
         if lifo {
            // Substitute s in t's place in treap.
            *pt = s
            s.ticket = t.ticket
            s.acquiretime = t.acquiretime
            s.parent = t.parent
            s.prev = t.prev
            s.next = t.next
            if s.prev != nil {
               s.prev.parent = s
            }
            if s.next != nil {
               s.next.parent = s
            }
            // Add t first in s's wait list.
            s.waitlink = t
            s.waittail = t.waittail
            if s.waittail == nil {
               s.waittail = t
            }
            t.parent = nil
            t.prev = nil
            t.next = nil
            t.waittail = nil
         } else {
            // Add s to end of t's wait list.
            if t.waittail == nil {
               t.waitlink = s
            } else {
               t.waittail.waitlink = s
            }
            t.waittail = s
            s.waitlink = nil
         }
         return
      }
      last = t
      if uintptr(unsafe.Pointer(addr)) < uintptr(t.elem) {
         pt = &t.prev
      } else {
         pt = &t.next
      }
   }

   // Add s as new leaf in tree of unique addrs.
   // The balanced tree is a treap using ticket as the random heap priority.
   // That is, it is a binary tree ordered according to the elem addresses,
   // but then among the space of possible binary trees respecting those
   // addresses, it is kept balanced on average by maintaining a heap ordering
   // on the ticket: s.ticket <= both s.prev.ticket and s.next.ticket.
   // https://en.wikipedia.org/wiki/Treap
   // https://faculty.washington.edu/aragon/pubs/rst89.pdf
   //
   // s.ticket compared with zero in couple of places, therefore set lowest bit.
   // It will not affect treap's quality noticeably.
   s.ticket = fastrand() | 1
   s.parent = last
   *pt = s

   // Rotate up into tree according to ticket (priority).
   for s.parent != nil && s.parent.ticket > s.ticket {
      if s.parent.prev == s {
         root.rotateRight(s.parent)
      } else {
         if s.parent.next != s {
            panic("semaRoot queue")
         }
         root.rotateLeft(s.parent)
      }
   }
}