Indeed, Dijkstra and colleagues invented the semaphore as a single primitive for all things related to synchronization; as you will see, one can use semaphores as both locks and condition variables.

Definition

A semaphore is an object with an integer value that we can manipulate with two routines; in the POSIX standard, these routines are sem_wait() P and sem_post() V.

int sem_wait(semt_t *s) {
  decrement the value of semephore s by one
  wait if value of semaphore s is negative
}
 
int sem_post(sem_t *s) {
  increment the value of semphore s by one
  if there are one or more threads waiting, wake one
}

Implementation

void P(csem) {
  while(1) {
    mutex_acquire(csem.mx);
    if (csem.value <= 0) {
      mutex_release(csem.mx);
      continue;
    } else {
      csem.value -= 1;
      mutex_release(csem.mx);
      break;
    }
  }
}
 
void V(csem) {
  mutex_acquire(csem.mx);
  csem.value += 1;
  mutex_release(csem.mx);
}

Re-think the P implementation. If the critical section is large, we could spend a great deal of time spinning.

Binary Semaphores (Locks)

@todo

reference