0
votes

I want to use semaphores to make threads block until the semaphores become positive; but when my threads block on a semaphore, they unblock immediately on the first sem_post called on it, regardless of the value of that semaphore afterwards.

The situation is as follows:

  1. Semaphore is set to -1.
  2. A thread is started to wait 5 seconds before posting to the semaphore.
  3. Once 5 seconds pass, the thread posts to the semaphore.
  4. The semaphore's value is 0, but the main process unblocks anyway.

Here is the code demonstrating the situation:

/**
 * testing whether sem_post on semaphore unblocks threads
 *     despite still being negative.
 **/
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

sem_t sem;

void *proc(void *arg)
{
    sleep(5);
    puts("posting");
    sem_post(&sem);
}

void setup()
{
    pthread_t pt;
    pthread_create(&pt, 0, proc, 0);
}

int main(int argc, char **argv)
{
    sem_init(&sem, 0, -1);

    setup();

    sem_wait(&sem);

    return 0;
}

I have found a crude way to overcome this issue by wrapping sem_wait in a busy loop that checks if the semaphore's value is 0, but this seems like a wrong way to approach this.

Is there a more elegant way to block on a semaphore until its value is positive (not 0, nor negative)?

2
The prototype for sem_init: int sem_init(sem_t *sem, int pshared, unsigned int value); Note the unsigned. Also, man sem_overview: A semaphore is an integer whose value is never allowed to fall below zero.[...].EOF
@EOF so you are saying instead of setting it to negative, I set it to a really large number? O.o. I'm confused, if that's the case, there is no way to tell how many threads are waiting on the semaphore without creating a new semaphore for keeping track of that(which is tricky to do because then i'll be forced to sem_getvalue before any sem_wait call). Is that the normal behavior?Dmitry
No, there is no way to tell how many processes are waiting for a semaphore from the sem_t. In fact, using a second semaphore to count the waiting processes would probably involve a race condition.EOF
Everything I learnt in operating system course is a lie :( But thanks! I got a lot to think about.Dmitry
Well, the semaphore is probably associated with some operating-system handle (like a futex), so the OS can schedule out a process that is blocked, and schedule it back in when the semaphore is posted. The OS will keep a list of processes blocked on the semaphore, but userspace doesn't usually see it.EOF

2 Answers

3
votes

You can't initialize the semaphore to -1 because the API to initialize it only takes unsigned values.

int sem_init(sem_t *sem, int pshared, unsigned int value);
The Open Group

Probably, using -1 caused a value larger than SEM_VALUE_MAX to be passed to sem_init(), and the initialization actually failed. You should check the return code, and if errno got set to EINVAL.

The sem_init() function will fail if:

  • [EINVAL] The value argument exceeds SEM_VALUE_MAX.
  • ...
    The Open Group
  • If you want threads to block on a semaphore on first call to sem_wait, initialize the semaphore to 0 instead.

    If your system supports it, you can use sem_getvalue to find the number of waiters on the semaphore if the count is 0.

    If sem is locked, then the value returned by sem_getvalue() is either zero or a negative number whose absolute value represents the number of processes waiting for the semaphore at some unspecified time during the call.
    The Open Group

    An implementation is allowed to choose which behavior they want, so this only works if your system supports it. Linux, for example, does not support it, since it chooses to return 0 if the semaphore count is 0.

    If one or more processes or threads are blocked waiting to lock the semaphore with sem_wait(3), POSIX.1 permits two possibilities for the value returned in sval: either 0 is returned; or a negative number whose absolute value is the count of the number of processes and threads currently blocked in sem_wait(3). Linux adopts the former behavior.
    man7.org

    Probably the most reliable way for you to implement what you want is to use a mutual exclusion variable, a condition variable, and a counter.

    extern pthread_mutex_t lock;
    extern pthread_cond_t queue;
    extern atomic_uint waiters;
    
    /* assume waiters is initially 0 */
    pthread_mutex_lock(&lock);
    ++waiters;
    while (my_status() == WAITING) {
        pthread_cond_wait(&queue, &lock);
    }
    --waiters;
    pthread_mutex_unlock(&lock);
    
    1
    votes

    Try initializing the semaphore with zero or a positive integer value. If you initialize with zero sem_init(&sem, 0, 0); you will see the desired behavior.