Search code examples
cmultithreadingsemaphore

Using semaphores to print out a specific pattern


Given these methods:

void* thread1Func(void* p) {
    while(1) {
        printf("A ");
    }
}

void* thread2Func(void* p) {
    while(1) {
        printf("B ");
    }
}

void* thread3Func(void* p) {
    while(1) {
        printf("C ");
    }
}

Assuming thread1 runs thread1Func, same for thread2 and 3, We're required to only add semaphores/semaphore methods/thread methods(creation, execution) such that the printed pattern will be A A B C A A B C A A B C A A B C ...

I've got code printing out the correct pattern, with no errors:

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

sem_t semaphoreA, semaphoreB, semaphoreC;

void* thread1Func(void* p) {
    while (1) {
        sem_wait(&semaphoreA);  // Wait for semaphoreA
        printf("A ");
        sem_post(&semaphoreA);  // Signal semaphoreA for the second 'A'
        sem_wait(&semaphoreA);  // Wait for semaphoreA again
        printf("A ");
        sem_post(&semaphoreB);  // Signal semaphoreB for 'B'
    }
}

void* thread2Func(void* p) {
    while (1) {
        sem_wait(&semaphoreB);  // Wait for semaphoreB
        printf("B ");
        sem_post(&semaphoreC);  // Signal semaphoreC for 'C'
    }
}

void* thread3Func(void* p) {
    while (1) {
        sem_wait(&semaphoreC);  // Wait for semaphoreC
        printf("C ");
        sem_post(&semaphoreA);  // Signal semaphoreA for the next 'A'
    }
}

int main() {
    pthread_t thread1, thread2, thread3;

    // Initialize semaphores
    sem_init(&semaphoreA, 0, 1);  // semaphoreA starts unlocked
    sem_init(&semaphoreB, 0, 0);  // semaphoreB starts locked
    sem_init(&semaphoreC, 0, 0);  // semaphoreC starts locked

    // Create threads
    pthread_create(&thread1, NULL, thread1Func, NULL);
    pthread_create(&thread2, NULL, thread2Func, NULL);
    pthread_create(&thread3, NULL, thread3Func, NULL);

    // Wait for threads to finish (which they won't in this case due to while(1))
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    pthread_join(thread3, NULL);

    // Destroy semaphores
    sem_destroy(&semaphoreA);
    sem_destroy(&semaphoreB);
    sem_destroy(&semaphoreC);

    return 0;
}

However this solution involves adding another print statement, which is not allowed.

I've tried different approachs to incrementing/decrementing the semaphores yet all either fail/ print the pattern

A B C A B C A B C A B C ...


I've played around with the wait/post methods, and also with the semaphore initial value, I've got this code that got me the correct pattern, however I am unsure on why this is working:

(added just the parts where I've changed values)

sem_t semaphoreA, semaphoreB, semaphoreC;

void* thread1Func(void* p) {
    while (1) {
        sem_wait(&semaphoreA);  // Wait for semaphoreA
        printf("A ");           // Print 'A'
        sem_post(&semaphoreB);  // Signal semaphoreB for 'B'
        sem_post(&semaphoreC);  // Signal semaphoreC for 'C'
    }
}

void* thread2Func(void* p) {
    while (1) {
        sem_wait(&semaphoreB);  // Wait for semaphoreB
        sem_wait(&semaphoreB);
        printf("B ");           // Signal semaphoreA for the next 'A'
    }
}

void* thread3Func(void* p) {
    while (1) {
        sem_wait(&semaphoreC);  // Wait for semaphoreC
        sem_wait(&semaphoreC);  
        printf("C ");          // Print 'C' and newline
        sem_post(&semaphoreA);  // Signal semaphoreA for the next 'A'
        sem_post(&semaphoreA);
    }
}

int main() {
    // Initialize semaphores
    sem_init(&semaphoreA, 0, 2);  // semaphoreA starts unlocked
    sem_init(&semaphoreB, 0, -1);  // semaphoreB starts locked
    sem_init(&semaphoreC, 0, -1);  // semaphoreC starts locked

    // ...
}

Solution

  • You're overthinking the problem. First, think about what has to happen at each logical step.

    1. Print 2 As
    2. Print 1 B
    3. Print 1 C

    Your A thread only cares about its turn and unblocking B, so start there.

    void* thread1Func(void* p) {                                                    
        while (1) {                                                                 
            sem_wait(&semaphoreA);  // Wait for semaphoreA                          
            printf("A ");                                                           
            sem_post(&semaphoreB);  // Signal semaphoreB for 'B'                    
        }                                                                           
    }
    

    Now to make B work, it needs to wait twice, since we always need two As.

    void* thread2Func(void* p) {                                                    
        while (1) {                                                                 
            sem_wait(&semaphoreB);  // Wait for semaphoreB                          
            sem_wait(&semaphoreB);  // Wait for semaphoreB                          
            printf("B ");                                                           
            sem_post(&semaphoreC);  // Signal semaphoreC for 'C'                    
        }                                                                           
    }
    

    Lastly, C needs to signal A twice since we want two prints.

    void* thread3Func(void* p) {
        while (1) {
            sem_wait(&semaphoreC);  // Wait for semaphoreC
            printf("C ");
            sem_post(&semaphoreA);  // Signal semaphoreA for the next 'A'
            sem_post(&semaphoreA);  // Signal semaphoreA for the next 'A'
        }
    }
    

    The only other thing you need to change is the initial value of semaphoreA to 2 (sem_init(&semaphoreA, 0, 2)) so that its loop will execute twice.