summaryrefslogtreecommitdiff
path: root/src/pkg/runtime/sema.goc
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/runtime/sema.goc')
-rw-r--r--src/pkg/runtime/sema.goc180
1 files changed, 180 insertions, 0 deletions
diff --git a/src/pkg/runtime/sema.goc b/src/pkg/runtime/sema.goc
new file mode 100644
index 000000000..ae84351ed
--- /dev/null
+++ b/src/pkg/runtime/sema.goc
@@ -0,0 +1,180 @@
+// 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 runtime
+#include "runtime.h"
+
+typedef struct Sema Sema;
+struct Sema
+{
+ uint32 volatile *addr;
+ G *g;
+ Sema *prev;
+ Sema *next;
+};
+
+typedef struct SemaRoot SemaRoot;
+struct SemaRoot
+{
+ Lock;
+ Sema *head;
+ Sema *tail;
+ // Number of waiters. Read w/o the lock.
+ uint32 volatile nwait;
+};
+
+// Prime to not correlate with any user patterns.
+#define SEMTABLESZ 251
+
+static union
+{
+ SemaRoot;
+ // Modern processors tend to have 64-byte cache lines,
+ // potentially with 128-byte effective cache line size for reading.
+ // While there are hypothetical architectures
+ // with 16-4096 byte cache lines, 128 looks like a good compromise.
+ uint8 pad[128];
+} semtable[SEMTABLESZ];
+
+static SemaRoot*
+semroot(uint32 *addr)
+{
+ return &semtable[((uintptr)addr >> 3) % SEMTABLESZ];
+}
+
+static void
+semqueue(SemaRoot *root, uint32 volatile *addr, Sema *s)
+{
+ s->g = 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, Sema *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 *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)
+{
+ Sema s;
+ SemaRoot *root;
+
+ // 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);
+ 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);
+ g->status = Gwaiting;
+ runtime·unlock(root);
+ runtime·gosched();
+ if(cansemacquire(addr))
+ return;
+ }
+}
+
+void
+runtime·semrelease(uint32 volatile *addr)
+{
+ Sema *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)
+ runtime·ready(s->g);
+}
+
+func Semacquire(addr *uint32) {
+ runtime·semacquire(addr);
+}
+
+func Semrelease(addr *uint32) {
+ runtime·semrelease(addr);
+}