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 }