// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Semaphore implementation exposed to Go. // Intended use is provide a sleep and wakeup // primitive that can be used in the contended case // of other synchronization primitives. // Thus it targets the same goal as Linux's futex, // but it has much simpler semantics. // // That is, don't think of these as semaphores. // Think of them as a way to implement sleep and wakeup // such that every sleep is paired with a single wakeup, // even if, due to races, the wakeup happens before the sleep. // // See Mullender and Cox, ``Semaphores in Plan 9,'' // http://swtch.com/semaphore.pdf package sync #include "runtime.h" #include "arch.h" typedef struct SemaWaiter SemaWaiter; struct SemaWaiter { uint32 volatile* addr; G* g; int64 releasetime; int32 nrelease; // -1 for acquire SemaWaiter* prev; SemaWaiter* next; }; typedef struct SemaRoot SemaRoot; struct SemaRoot { Lock; SemaWaiter* head; SemaWaiter* tail; // Number of waiters. Read w/o the lock. uint32 volatile nwait; }; // Prime to not correlate with any user patterns. #define SEMTABLESZ 251 struct semtable { SemaRoot; uint8 pad[CacheLineSize-sizeof(SemaRoot)]; }; static struct semtable semtable[SEMTABLESZ]; static SemaRoot* semroot(uint32 volatile *addr) { return &semtable[((uintptr)addr >> 3) % SEMTABLESZ]; } static void semqueue(SemaRoot *root, uint32 volatile *addr, SemaWaiter *s) { s->g = runtime_g(); s->addr = addr; s->next = nil; s->prev = root->tail; if(root->tail) root->tail->next = s; else root->head = s; root->tail = s; } static void semdequeue(SemaRoot *root, SemaWaiter *s) { if(s->next) s->next->prev = s->prev; else root->tail = s->prev; if(s->prev) s->prev->next = s->next; else root->head = s->next; s->prev = nil; s->next = nil; } static int32 cansemacquire(uint32 volatile *addr) { uint32 v; while((v = runtime_atomicload(addr)) > 0) if(runtime_cas(addr, v, v-1)) return 1; return 0; } void runtime_semacquire(uint32 volatile *addr, bool profile) { SemaWaiter s; // Needs to be allocated on stack, otherwise garbage collector could deallocate it SemaRoot *root; int64 t0; // 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) root = semroot(addr); t0 = 0; s.releasetime = 0; if(profile && runtime_blockprofilerate > 0) { t0 = runtime_cputicks(); s.releasetime = -1; } for(;;) { runtime_lock(root); // Add ourselves to nwait to disable "easy case" in semrelease. runtime_xadd(&root->nwait, 1); // Check cansemacquire to avoid missed wakeup. if(cansemacquire(addr)) { runtime_xadd(&root->nwait, -1); runtime_unlock(root); return; } // Any semrelease after the cansemacquire knows we're waiting // (we set nwait above), so go to sleep. semqueue(root, addr, &s); runtime_park(runtime_unlock, root, "semacquire"); if(cansemacquire(addr)) { if(t0) runtime_blockevent(s.releasetime - t0, 3); return; } } } void runtime_semrelease(uint32 volatile *addr) { SemaWaiter *s; SemaRoot *root; root = semroot(addr); runtime_xadd(addr, 1); // Easy case: no waiters? // This check must happen after the xadd, to avoid a missed wakeup // (see loop in semacquire). if(runtime_atomicload(&root->nwait) == 0) return; // Harder case: search for a waiter and wake it. runtime_lock(root); if(runtime_atomicload(&root->nwait) == 0) { // The count is already consumed by another goroutine, // so no need to wake up another goroutine. runtime_unlock(root); return; } for(s = root->head; s; s = s->next) { if(s->addr == addr) { runtime_xadd(&root->nwait, -1); semdequeue(root, s); break; } } runtime_unlock(root); if(s) { if(s->releasetime) s->releasetime = runtime_cputicks(); runtime_ready(s->g); } } // TODO(dvyukov): move to netpoll.goc once it's used by all OSes. void net_runtime_Semacquire(uint32 *addr) __asm__ (GOSYM_PREFIX "net.runtime_Semacquire"); void net_runtime_Semacquire(uint32 *addr) { runtime_semacquire(addr, true); } void net_runtime_Semrelease(uint32 *addr) __asm__ (GOSYM_PREFIX "net.runtime_Semrelease"); void net_runtime_Semrelease(uint32 *addr) { runtime_semrelease(addr); } func runtime_Semacquire(addr *uint32) { runtime_semacquire(addr, true); } func runtime_Semrelease(addr *uint32) { runtime_semrelease(addr); } typedef struct SyncSema SyncSema; struct SyncSema { Lock; SemaWaiter* head; SemaWaiter* tail; }; func runtime_Syncsemcheck(size uintptr) { if(size != sizeof(SyncSema)) { runtime_printf("bad SyncSema size: sync:%D runtime:%D\n", (int64)size, (int64)sizeof(SyncSema)); runtime_throw("bad SyncSema size"); } } // Syncsemacquire waits for a pairing Syncsemrelease on the same semaphore s. func runtime_Syncsemacquire(s *SyncSema) { SemaWaiter w, *wake; int64 t0; w.g = runtime_g(); w.nrelease = -1; w.next = nil; w.releasetime = 0; t0 = 0; if(runtime_blockprofilerate > 0) { t0 = runtime_cputicks(); w.releasetime = -1; } runtime_lock(s); if(s->head && s->head->nrelease > 0) { // have pending release, consume it wake = nil; s->head->nrelease--; if(s->head->nrelease == 0) { wake = s->head; s->head = wake->next; if(s->head == nil) s->tail = nil; } runtime_unlock(s); if(wake) runtime_ready(wake->g); } else { // enqueue itself if(s->tail == nil) s->head = &w; else s->tail->next = &w; s->tail = &w; runtime_park(runtime_unlock, s, "semacquire"); if(t0) runtime_blockevent(w.releasetime - t0, 2); } } // Syncsemrelease waits for n pairing Syncsemacquire on the same semaphore s. func runtime_Syncsemrelease(s *SyncSema, n uint32) { SemaWaiter w, *wake; w.g = runtime_g(); w.nrelease = (int32)n; w.next = nil; w.releasetime = 0; runtime_lock(s); while(w.nrelease > 0 && s->head && s->head->nrelease < 0) { // have pending acquire, satisfy it wake = s->head; s->head = wake->next; if(s->head == nil) s->tail = nil; if(wake->releasetime) wake->releasetime = runtime_cputicks(); runtime_ready(wake->g); w.nrelease--; } if(w.nrelease > 0) { // enqueue itself if(s->tail == nil) s->head = &w; else s->tail->next = &w; s->tail = &w; runtime_park(runtime_unlock, s, "semarelease"); } else runtime_unlock(s); }