13

In the man page it appears that even if you initialise a semaphore to a value of one:

sem_init(&mySem, 0, 1);

It could still be incremented to a value greater than 1 with multiple calls to

sem_post(&mySem);

But in this code example the comment seems to think differently:

sem_init(&mutex, 0, 1);      /* initialize mutex to 1 - binary semaphore */

Is it possible to initialise a strictly binary semaphore in C?

Note: The reason for doing this instead of using a mutex in this case is the sem_post and sem_wait may be called by different threads.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
austinmarton
  • 2,278
  • 3
  • 20
  • 23

3 Answers3

11

If you want a strictly binary semaphore on Linux, I suggest building one out of mutexes and condition variables.

struct binary_semaphore {
    pthread_mutex_t mutex;
    pthread_cond_t cvar;
    bool v;
};

void mysem_post(struct binary_semaphore *p)
{
    pthread_mutex_lock(&p->mutex);
    if (p->v)
        abort(); // error
    p->v = true;
    pthread_cond_signal(&p->cvar);
    pthread_mutex_unlock(&p->mutex);
}

void mysem_wait(struct binary_semaphore *p)
{
    pthread_mutex_lock(&p->mutex);
    while (!p->v)
        pthread_cond_wait(&p->cvar, &p->mutex);
    p->v = false;
    pthread_mutex_unlock(&p->mutex);
}
Dietrich Epp
  • 205,541
  • 37
  • 345
  • 415
  • What exactly is the point of the `while` loop in `mysem_wait`? Won't a simple `if`-statement do the job since `pthread_cond_wait` will reaquire the mutex after being signaled and `pthread_cond_signal` only unblocks a single thread waiting on the condition? – Bill DeRose Dec 14 '13 at 00:09
  • 5
    @BillDeRose: Condition variables are subject to spurious wakeups and stolen wakeups. You **must** use them in a loop if you want to check for a condition. – Dietrich Epp Dec 14 '13 at 00:53
  • @DietrichEpp I can't understand, supose that you call wait before calling post. p->mutex is locked so if you call sem_post the thread will lock in sem_post and everything will locked (I'm sure that I'm wrong but I don't get it)If pthread_cond_signal unlock the mutex, why do you use additional mutex unlock – Mquinteiro Feb 20 '19 at 06:00
  • 1
    @Mquinteiro: Read the manual page for pthread_cond_wait. It unlocks the mutex while it is waiting. I don’t see any extra mutex unlock here, each call to lock() is paired with a call to unlock() at the end of the function. – Dietrich Epp Feb 20 '19 at 15:18
  • @DietrichEpp that means that when is signaled and wait the finish p->mutex is locked again? – Mquinteiro Feb 20 '19 at 16:38
  • This answer doesn't work like a binary semaphore because Pthread_cond_signal is missed if no thread is waiting for the signal. I have an example to prove that – abdullam Jul 22 '19 at 19:41
  • @abdullam: This is an incorrect analysis. It is not necessary for `pthread_cond_signal` to wake up a thread under all circumstances, it is only necessary to wake up a thread if one is already waiting. If the thread is not waiting when `pthread_cond_signal` is called, then the other thread will see `p->v == 1` and not call `pthread_cond_wait`. Unfortunately the example you have found contains a data race, this can prevent it from working correctly. – Dietrich Epp Jul 22 '19 at 21:52
  • @DietrichEpp Than's exactly my point. pthread_cond_signal doesn't wake up a thread unless the other thread is already waiting for it, BUT sem_post wakes up a thread even though the thread is not in sem_wait. In short, sem_post cannot be missed but pthread_cond_sign.al can be missed if thread not currently waiting for it. What am I missing here? And about my contrived example to show this - I googled "data race" and found the following from Oracles description ... "Some data-races may be benign (for example, when the memory access is used for a busy-wait)".. which I am doing. Thanks – abdullam Jul 23 '19 at 20:59
  • @abdullam: sem_post will not wake up a thread if the other thread is not in sem_wait, because there is no thread waiting. If a thread is not waiting, you cannot wake it up. To "wake up" a thread means to make a thread stop waiting. The data race exists in your code because there are operations on different threads (reading and writing `thread_2_working`) that access the same variable, not all of the operations are reads, and the accesses are not synchronized. This is a textbook data race, and it makes the code incorrect. You **must** fix your code because it is **broken.** – Dietrich Epp Jul 23 '19 at 21:58
  • So, if I'm understanding this code correctly, waiting locks the mutex for some reason, probably to prevent race conditions, then ensures the waiting threads are blocked, giving the posting threads the ability to lock it and then to unblock the calling threads. The mutex, presumably, is just for ensuring there are no race conditions w.r.t p->v. Is that correct? – Montana Burr Jun 02 '20 at 18:32
  • @MontanaBurr: Sure, you can think of the mutex as preventing race conditions. Imagine a semaphore without the mutex. What happens if two threads call wait() at the same time? Maybe both of them succeed! What happens if one thread calls wait() and one thread calls post() at the same time? Maybe pthread_cond_wait() will get called after pthread_cond_signal(), so the waiting thread will wait forever. **The mutex is a critical part.** Note that you can’t even use a condition variable without a mutex, and you **must** lock the mutex before calling pthread_cond_wait(). – Dietrich Epp Jun 02 '20 at 19:37
0

you can create a wrapper function for sem_post that will check the semaphore value before sem_post().

example:

int semaphore_give(sem_t *sem)
{
    int value, ret;

    if (!sem)
        return -1;
    
    if (sem_getvalue(sem, &value))
        return -1;

    if (value == 0)
        return sem_post(sem);
    return -1;
}
-1

I don't like the implementation of semaphores in Linux but have to use it. To make it simple I decrement sem value till zero with sem_trywait if it is returned one on the first check:

if (sem_trywait(pProcess->sem_id) < 0 && errno != EINTR ) {
    return ERROR;
}
else {
    while (sem_trywait(pProcess->sem_id) == 0);
    return OK;
}

Be careful if sem_post is called too often. You can stuck in the while() clause. In my case it's called no more than once per second.

Peter
  • 123
  • 1
  • 6