120 lines
3.3 KiB
C
120 lines
3.3 KiB
C
|
/* go-semacquire.c -- implement runtime.Semacquire and runtime.Semrelease.
|
||
|
|
||
|
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. */
|
||
|
|
||
|
#include <stdint.h>
|
||
|
|
||
|
#include <pthread.h>
|
||
|
|
||
|
#include "go-assert.h"
|
||
|
#include "runtime.h"
|
||
|
|
||
|
/* We use a single global lock and condition variable. This is
|
||
|
painful, since it will cause unnecessary contention, but is hard to
|
||
|
avoid in a portable manner. On Linux we can use futexes, but they
|
||
|
are unfortunately not exposed by libc and are thus also hard to use
|
||
|
portably. */
|
||
|
|
||
|
static pthread_mutex_t sem_lock = PTHREAD_MUTEX_INITIALIZER;
|
||
|
static pthread_cond_t sem_cond = PTHREAD_COND_INITIALIZER;
|
||
|
|
||
|
/* If the value in *ADDR is positive, and we are able to atomically
|
||
|
decrement it, return true. Otherwise do nothing and return
|
||
|
false. */
|
||
|
|
||
|
static _Bool
|
||
|
acquire (uint32 *addr)
|
||
|
{
|
||
|
while (1)
|
||
|
{
|
||
|
uint32 val;
|
||
|
|
||
|
val = *addr;
|
||
|
if (val == 0)
|
||
|
return 0;
|
||
|
if (__sync_bool_compare_and_swap (addr, val, val - 1))
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Implement runtime.Semacquire. ADDR points to a semaphore count.
|
||
|
We have acquired the semaphore when we have decremented the count
|
||
|
and it remains nonnegative. */
|
||
|
|
||
|
void
|
||
|
semacquire (uint32 *addr)
|
||
|
{
|
||
|
while (1)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
/* If the current count is positive, and we are able to atomically
|
||
|
decrement it, then we have acquired the semaphore. */
|
||
|
if (acquire (addr))
|
||
|
return;
|
||
|
|
||
|
/* Lock the mutex. */
|
||
|
i = pthread_mutex_lock (&sem_lock);
|
||
|
__go_assert (i == 0);
|
||
|
|
||
|
/* Check the count again with the mutex locked. */
|
||
|
if (acquire (addr))
|
||
|
{
|
||
|
i = pthread_mutex_unlock (&sem_lock);
|
||
|
__go_assert (i == 0);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* The count is zero. Even if a call to runtime.Semrelease
|
||
|
increments it to become positive, that call will try to
|
||
|
acquire the mutex and block, so we are sure to see the signal
|
||
|
of the condition variable. */
|
||
|
i = pthread_cond_wait (&sem_cond, &sem_lock);
|
||
|
__go_assert (i == 0);
|
||
|
|
||
|
/* Unlock the mutex and try again. */
|
||
|
i = pthread_mutex_unlock (&sem_lock);
|
||
|
__go_assert (i == 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Implement runtime.Semrelease. ADDR points to a semaphore count. We
|
||
|
must atomically increment the count. If the count becomes
|
||
|
positive, we signal the condition variable to wake up another
|
||
|
process. */
|
||
|
|
||
|
void
|
||
|
semrelease (uint32 *addr)
|
||
|
{
|
||
|
int32_t val;
|
||
|
|
||
|
val = __sync_fetch_and_add (addr, 1);
|
||
|
|
||
|
/* VAL is the old value. It should never be negative. If it is
|
||
|
negative, that implies that Semacquire somehow decremented a zero
|
||
|
value, or that the count has overflowed. */
|
||
|
__go_assert (val >= 0);
|
||
|
|
||
|
/* If the old value was zero, then we have now released a count, and
|
||
|
we signal the condition variable. If the old value was positive,
|
||
|
then nobody can be waiting. We have to use
|
||
|
pthread_cond_broadcast, not pthread_cond_signal, because
|
||
|
otherwise there would be a race condition when the count is
|
||
|
incremented twice before any locker manages to decrement it. */
|
||
|
if (val == 0)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
i = pthread_mutex_lock (&sem_lock);
|
||
|
__go_assert (i == 0);
|
||
|
|
||
|
i = pthread_cond_broadcast (&sem_cond);
|
||
|
__go_assert (i == 0);
|
||
|
|
||
|
i = pthread_mutex_unlock (&sem_lock);
|
||
|
__go_assert (i == 0);
|
||
|
}
|
||
|
}
|