github.com/aloncn/graphics-go@v0.0.1/src/runtime/sema.go (about)

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Semaphore implementation exposed to Go.
     6  // Intended use is provide a sleep and wakeup
     7  // primitive that can be used in the contended case
     8  // of other synchronization primitives.
     9  // Thus it targets the same goal as Linux's futex,
    10  // but it has much simpler semantics.
    11  //
    12  // That is, don't think of these as semaphores.
    13  // Think of them as a way to implement sleep and wakeup
    14  // such that every sleep is paired with a single wakeup,
    15  // even if, due to races, the wakeup happens before the sleep.
    16  //
    17  // See Mullender and Cox, ``Semaphores in Plan 9,''
    18  // http://swtch.com/semaphore.pdf
    19  
    20  package runtime
    21  
    22  import (
    23  	"runtime/internal/atomic"
    24  	"runtime/internal/sys"
    25  	"unsafe"
    26  )
    27  
    28  // Asynchronous semaphore for sync.Mutex.
    29  
    30  type semaRoot struct {
    31  	lock  mutex
    32  	head  *sudog
    33  	tail  *sudog
    34  	nwait uint32 // Number of waiters. Read w/o the lock.
    35  }
    36  
    37  // Prime to not correlate with any user patterns.
    38  const semTabSize = 251
    39  
    40  var semtable [semTabSize]struct {
    41  	root semaRoot
    42  	pad  [sys.CacheLineSize - unsafe.Sizeof(semaRoot{})]byte
    43  }
    44  
    45  //go:linkname sync_runtime_Semacquire sync.runtime_Semacquire
    46  func sync_runtime_Semacquire(addr *uint32) {
    47  	semacquire(addr, true)
    48  }
    49  
    50  //go:linkname net_runtime_Semacquire net.runtime_Semacquire
    51  func net_runtime_Semacquire(addr *uint32) {
    52  	semacquire(addr, true)
    53  }
    54  
    55  //go:linkname sync_runtime_Semrelease sync.runtime_Semrelease
    56  func sync_runtime_Semrelease(addr *uint32) {
    57  	semrelease(addr)
    58  }
    59  
    60  //go:linkname net_runtime_Semrelease net.runtime_Semrelease
    61  func net_runtime_Semrelease(addr *uint32) {
    62  	semrelease(addr)
    63  }
    64  
    65  // Called from runtime.
    66  func semacquire(addr *uint32, profile bool) {
    67  	gp := getg()
    68  	if gp != gp.m.curg {
    69  		throw("semacquire not on the G stack")
    70  	}
    71  
    72  	// Easy case.
    73  	if cansemacquire(addr) {
    74  		return
    75  	}
    76  
    77  	// Harder case:
    78  	//	increment waiter count
    79  	//	try cansemacquire one more time, return if succeeded
    80  	//	enqueue itself as a waiter
    81  	//	sleep
    82  	//	(waiter descriptor is dequeued by signaler)
    83  	s := acquireSudog()
    84  	root := semroot(addr)
    85  	t0 := int64(0)
    86  	s.releasetime = 0
    87  	if profile && blockprofilerate > 0 {
    88  		t0 = cputicks()
    89  		s.releasetime = -1
    90  	}
    91  	for {
    92  		lock(&root.lock)
    93  		// Add ourselves to nwait to disable "easy case" in semrelease.
    94  		atomic.Xadd(&root.nwait, 1)
    95  		// Check cansemacquire to avoid missed wakeup.
    96  		if cansemacquire(addr) {
    97  			atomic.Xadd(&root.nwait, -1)
    98  			unlock(&root.lock)
    99  			break
   100  		}
   101  		// Any semrelease after the cansemacquire knows we're waiting
   102  		// (we set nwait above), so go to sleep.
   103  		root.queue(addr, s)
   104  		goparkunlock(&root.lock, "semacquire", traceEvGoBlockSync, 4)
   105  		if cansemacquire(addr) {
   106  			break
   107  		}
   108  	}
   109  	if s.releasetime > 0 {
   110  		blockevent(int64(s.releasetime)-t0, 3)
   111  	}
   112  	releaseSudog(s)
   113  }
   114  
   115  func semrelease(addr *uint32) {
   116  	root := semroot(addr)
   117  	atomic.Xadd(addr, 1)
   118  
   119  	// Easy case: no waiters?
   120  	// This check must happen after the xadd, to avoid a missed wakeup
   121  	// (see loop in semacquire).
   122  	if atomic.Load(&root.nwait) == 0 {
   123  		return
   124  	}
   125  
   126  	// Harder case: search for a waiter and wake it.
   127  	lock(&root.lock)
   128  	if atomic.Load(&root.nwait) == 0 {
   129  		// The count is already consumed by another goroutine,
   130  		// so no need to wake up another goroutine.
   131  		unlock(&root.lock)
   132  		return
   133  	}
   134  	s := root.head
   135  	for ; s != nil; s = s.next {
   136  		if s.elem == unsafe.Pointer(addr) {
   137  			atomic.Xadd(&root.nwait, -1)
   138  			root.dequeue(s)
   139  			break
   140  		}
   141  	}
   142  	unlock(&root.lock)
   143  	if s != nil {
   144  		if s.releasetime != 0 {
   145  			s.releasetime = cputicks()
   146  		}
   147  		goready(s.g, 4)
   148  	}
   149  }
   150  
   151  func semroot(addr *uint32) *semaRoot {
   152  	return &semtable[(uintptr(unsafe.Pointer(addr))>>3)%semTabSize].root
   153  }
   154  
   155  func cansemacquire(addr *uint32) bool {
   156  	for {
   157  		v := atomic.Load(addr)
   158  		if v == 0 {
   159  			return false
   160  		}
   161  		if atomic.Cas(addr, v, v-1) {
   162  			return true
   163  		}
   164  	}
   165  }
   166  
   167  func (root *semaRoot) queue(addr *uint32, s *sudog) {
   168  	s.g = getg()
   169  	s.elem = unsafe.Pointer(addr)
   170  	s.next = nil
   171  	s.prev = root.tail
   172  	if root.tail != nil {
   173  		root.tail.next = s
   174  	} else {
   175  		root.head = s
   176  	}
   177  	root.tail = s
   178  }
   179  
   180  func (root *semaRoot) dequeue(s *sudog) {
   181  	if s.next != nil {
   182  		s.next.prev = s.prev
   183  	} else {
   184  		root.tail = s.prev
   185  	}
   186  	if s.prev != nil {
   187  		s.prev.next = s.next
   188  	} else {
   189  		root.head = s.next
   190  	}
   191  	s.elem = nil
   192  	s.next = nil
   193  	s.prev = nil
   194  }
   195  
   196  // Synchronous semaphore for sync.Cond.
   197  type syncSema struct {
   198  	lock mutex
   199  	head *sudog
   200  	tail *sudog
   201  }
   202  
   203  // syncsemacquire waits for a pairing syncsemrelease on the same semaphore s.
   204  //go:linkname syncsemacquire sync.runtime_Syncsemacquire
   205  func syncsemacquire(s *syncSema) {
   206  	lock(&s.lock)
   207  	if s.head != nil && s.head.nrelease > 0 {
   208  		// Have pending release, consume it.
   209  		var wake *sudog
   210  		s.head.nrelease--
   211  		if s.head.nrelease == 0 {
   212  			wake = s.head
   213  			s.head = wake.next
   214  			if s.head == nil {
   215  				s.tail = nil
   216  			}
   217  		}
   218  		unlock(&s.lock)
   219  		if wake != nil {
   220  			wake.next = nil
   221  			goready(wake.g, 4)
   222  		}
   223  	} else {
   224  		// Enqueue itself.
   225  		w := acquireSudog()
   226  		w.g = getg()
   227  		w.nrelease = -1
   228  		w.next = nil
   229  		w.releasetime = 0
   230  		t0 := int64(0)
   231  		if blockprofilerate > 0 {
   232  			t0 = cputicks()
   233  			w.releasetime = -1
   234  		}
   235  		if s.tail == nil {
   236  			s.head = w
   237  		} else {
   238  			s.tail.next = w
   239  		}
   240  		s.tail = w
   241  		goparkunlock(&s.lock, "semacquire", traceEvGoBlockCond, 3)
   242  		if t0 != 0 {
   243  			blockevent(int64(w.releasetime)-t0, 2)
   244  		}
   245  		releaseSudog(w)
   246  	}
   247  }
   248  
   249  // syncsemrelease waits for n pairing syncsemacquire on the same semaphore s.
   250  //go:linkname syncsemrelease sync.runtime_Syncsemrelease
   251  func syncsemrelease(s *syncSema, n uint32) {
   252  	lock(&s.lock)
   253  	for n > 0 && s.head != nil && s.head.nrelease < 0 {
   254  		// Have pending acquire, satisfy it.
   255  		wake := s.head
   256  		s.head = wake.next
   257  		if s.head == nil {
   258  			s.tail = nil
   259  		}
   260  		if wake.releasetime != 0 {
   261  			wake.releasetime = cputicks()
   262  		}
   263  		wake.next = nil
   264  		goready(wake.g, 4)
   265  		n--
   266  	}
   267  	if n > 0 {
   268  		// enqueue itself
   269  		w := acquireSudog()
   270  		w.g = getg()
   271  		w.nrelease = int32(n)
   272  		w.next = nil
   273  		w.releasetime = 0
   274  		if s.tail == nil {
   275  			s.head = w
   276  		} else {
   277  			s.tail.next = w
   278  		}
   279  		s.tail = w
   280  		goparkunlock(&s.lock, "semarelease", traceEvGoBlockCond, 3)
   281  		releaseSudog(w)
   282  	} else {
   283  		unlock(&s.lock)
   284  	}
   285  }
   286  
   287  //go:linkname syncsemcheck sync.runtime_Syncsemcheck
   288  func syncsemcheck(sz uintptr) {
   289  	if sz != unsafe.Sizeof(syncSema{}) {
   290  		print("runtime: bad syncSema size - sync=", sz, " runtime=", unsafe.Sizeof(syncSema{}), "\n")
   291  		throw("bad syncSema size")
   292  	}
   293  }