summaryrefslogtreecommitdiffstats
path: root/libgo/runtime/go-semacquire.c
blob: 67a86ef695f930ee2d806ba446204684c4a65a72 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/* 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);
    }
}
OpenPOWER on IntegriCloud