Search code examples
clinuxposix

POSIX semaphores in C but still have race conditions?


I am trying to understand semaphores, but I cannot figure it out. I think I still have race conditions in my code

The concept is quite simple start this program 4 times using a command line argument -a, -b,-c or -d. Starting order should not matter, but with the following code (see below) it does, and I am not quite sure why.

The printed output should 1 2 3 4 5 6 7 8 in the end.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/fcntl.h>

sem_t *semdes = SEM_FAILED;
char *sem_name = "test";

int main(int argc, char *const *argv)
{
    int opt, res;
    semdes = sem_open(sem_name, O_CREAT | O_EXCL, 0600, 4);
    printf("sem_open() returned %p\n", semdes);
    if (semdes == SEM_FAILED)
    {
        semdes = sem_open(sem_name, 0);
    }
    if ((opt = getopt(argc, argv, "abcd:")) != -1)
    {
        switch (opt)
        {
        case 'a':
            //printNumbers(1, 5);
            sem_wait(semdes);
            res = sem_wait(semdes);
            if (res != 0)
            {
                perror("ERROR: sem_wait() failed");
            }
            printf("sem_wait() returned %d\n", res);
            printf("%d\n", 1);
            sleep(1);
            sem_post(semdes);
            sem_wait(semdes);
            res = sem_wait(semdes);
            if (res != 0)
            {
                perror("ERROR: sem_wait() failed");
            }
            printf("sem_wait() returned %d\n", res);
            printf("%d\n", 5);
            sleep(1);
            sem_post(semdes);

            break;
        case 'b':
            sem_wait(semdes);
            res = sem_wait(semdes);
            if (res != 0)
            {
                perror("ERROR: sem_wait() failed");
            }
            printf("sem_wait() returned %d\n", res);
            printf("%d\n", 2);
            sleep(1);
            sem_post(semdes);
            //printNumbers(2, 6);
            break;
        case 'c':
            sem_wait(semdes);
            res = sem_wait(semdes);
            if (res != 0)
            {
                perror("ERROR: sem_wait() failed");
            }
            printf("sem_wait() returned %d\n", res);
            printf("%d\n", 3);
            sleep(1);
            sem_post(semdes);
            //printNumbers(3, 7);
            break;
        case 'd':
            sem_wait(semdes);
            res = sem_wait(semdes);
            if (res != 0)
            {
                perror("ERROR: sem_wait() failed");
            }
            printf("sem_wait() returned %d\n", res);
            printf("%d\n", 4);
            sleep(1);
            sem_post(semdes);
            //printNumbers(4, 8);
            break;
        default:
            fprintf(stderr, "ERROR: unknown option '%c'\n", opt);
            exit(1);
            break;
        }
    }
    else
    {
        exit(1);
    }

    return 0;
}

I think I fail to understand how semaphores work or I am using the sem_open incorrectly.

Using Ubuntu 18.04

EDIT This the current output generated

sem_open() returned 0x7f5fdbcbf000
sem_wait() returned 0
2
sem_open() returned (nil)
sem_wait() returned 0
3
sem_open() returned (nil)
sem_open() returned (nil)
./n: option requires an argument -- 'd'
ERROR: unknown option '?'
sem_wait() returned 0
1
^C

Solution

  • I solved the problem thanks to Christian idea of using multiple semaphores!

    Final code !

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <errno.h>
    #include <semaphore.h>
    #include <sys/types.h>
    #include <sys/mman.h>
    #include <sys/stat.h>
    #include <sys/fcntl.h>
    
    sem_t *semA = SEM_FAILED;
    sem_t *semB = SEM_FAILED;
    sem_t *semC = SEM_FAILED;
    sem_t *semD = SEM_FAILED;
    sem_t *sem1 = SEM_FAILED;
    sem_t *sem2 = SEM_FAILED;
    char *nameA = "testa";
    char *nameB = "testb";
    char *nameC = "testc";
    char *nameD = "testd";
    
    int printNumbers(char *name1, char *name2, int no)
    {
        //open own semaphore first
        sem1 = sem_open(name1, O_CREAT, 0600, 0);
        int res = sem_wait(sem1);
        if (res != 0)
        {
            perror("PROG: ERROR: sem_wait() failed");
        }
        //printf("sem_wait() of %s returned %d\n", name1, res);
        printf("%d\n", no);
        sem_close(sem1);
        sleep(5);
        //open next semaphore
        sem2 = sem_open(name2, O_CREAT, 0600, 1);
        sem_post(sem2);
        sem_close(sem2);
        return res;
    }
    
    void cleanUp()
    {
        char name[6] = "testa";
        int res = 0;
        for (size_t i = 0; i < 4; i++)
        {
            res = sem_unlink(name);
            if (res != 0)
            {
                perror("ERROR: sem_unlink() failed");
            }
            printf("sem_unlink() returned %d\n", res);
            name[4] = name[4]+1;
        }
    }
    
    int main(int argc, char *const *argv)
    {
        int opt, res;
    
        if ((opt = getopt(argc, argv, "abcd")) != -1)
        {
            switch (opt)
            {
            case 'a':
                // Guarantees 1 is always first since no semaphore
                printf("\n%d\n", 1);
                sleep(5);
                semB = sem_open(nameB, O_CREAT, 0600, 1);
                sem_post(semB);
                sleep(5);
                sem_close(semB);
                printNumbers(nameA, nameB, 5);
                break;
            case 'b':
                printNumbers(nameB, nameC, 2);
                printNumbers(nameB, nameC, 6);
                break;
            case 'c':
                printNumbers(nameC, nameD, 3);
                printNumbers(nameC, nameD, 7);
                break;
            case 'd':
                printNumbers(nameD, nameA, 4);
                printNumbers(nameD, nameA, 8);
                cleanUp();
                break;
            default:
                fprintf(stderr, "ERROR: unknown option '%c'\n", opt);
                exit(1);
                break;
            }
        }
        else
        {
            exit(1);
        }
        return 0;
    }
    

    With output as :

    1
    2
    3
    4
    5
    6
    7
    8
    sem_unlink() returned 0
    sem_unlink() returned 0
    sem_unlink() returned 0
    sem_unlink() returned 0