0

I am working on a simulator for an embedded operating system that uses 5 threads. One acts as the scheduler thread and the other 4 are worker threads. The order of operation is always the same: the scheduler executes between every other thread and goes on activating the one that should go next, like this: SchThread Thread1 SchThread Thread2 SchThread Thread3 SchThread Thread4 SchThread Thread1 ... and so on.

This is currently done in Windows with some bad practices and I'm tasked with switching it to a pthreads implementation. I know this is not how threads are supposed to work because the workload is fully sequential, but the simulator relies on having the 5 threads mentioned.

My question is: how am I supposed to implement this behaviour? Are mutexes or conditions enough? Should I use barriers? Maybe signals from the scheduler to wake up worker threads?

I've seen posts like this one (execution of pthreads in a particular order) that works with 2 threads, but I've failed in my attempts to increase the number to 5. Is using pthread_cond_ a good solution to this problem or am I focusing it wrong?

I've also tried doing the synchronization using mutexes or semaphores unsuccessfully. I've thought of a shared variable that manages the turn but I've been unable to make it work properly.

Zya
  • 27
  • 6
  • Mutexes or events should work. Maybe you could include relevant parts of the code which isn't working? – Lundin Jan 03 '23 at 10:19
  • 1
    @Lundin, Not Mutexes. Trying to use a mutex for signalling beetween threads can only lead to frustration. The only way to use a mutex is; some thread locks it in order to gain exclusive use of some shared resource, and then the _same thread_ unlocks it when it's finished. In many languages/libraries/operating-systems, attempting to "unlock" something called "mutex" in a different thread from the one that locked it will cause an error. Use a [_semaphore_](https://man7.org/linux/man-pages/man7/sem_overview.7.html) instead of a mutex when you want one thread to open a "gate" for another. – Solomon Slow Jan 03 '23 at 14:26
  • @SolomonSlow Pedantically yeah. In practice, you can efficiently `WaitFor...` a Windows mutex just like any other resource. You can even use it on thread handles. As for what some pthread abstraction layer might do on top of it, I'm not sure. – Lundin Jan 03 '23 at 14:33
  • @Lundin, "mutex" can mean slightly different things and have slightly different behavior in different systems. But even in systems where mutex is more semaphore-like, your program will be easier for others to read and understand if you only use "mutex" or "lock" as I described it above, and you use "semaphore" for everything else. – Solomon Slow Jan 03 '23 at 14:39
  • @SolomonSlow Or just use events as I proposed in the first comment, since the purpose here is synchronisation and not thread safety. – Lundin Jan 03 '23 at 14:40

2 Answers2

1

This is an opinion, because there's more than one way to solve your problem.

I would use a shared global variable, currentThread, whose value identifies which thread should run, and I would use a condition variable to let threads notify each other when currentThread changes.

Sorry I don't have time to write an example at this moment, but you can find examples in the man pages for pthread_cond_t, pthread_cond_wait(...), and pthread_cond_broadcast(...)

Solomon Slow
  • 25,130
  • 5
  • 37
  • 57
  • I just posted a solution I wrote that I think does exactly what you are saying. A global variable `turno` that identifies the running thread and conditions for every thread to stop/start. Thank you for your answer – Zya Jan 03 '23 at 14:43
0

I've been thinking on asking this for a few days and the day I post the question I find a possible solution to this problem.

Continuing the explanation of the answer linked in the question, the solution looks like this:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>  /* sleep */

#define MS100 100000
#define HALFS 500000

pthread_mutex_t m_sch;
pthread_cond_t  SCH_GO;
pthread_cond_t  THREAD2_GO;
pthread_cond_t  THREAD3_GO;


unsigned turno = 1;

/* get next turn */
int sig_turno() {
    static int anterior = 0;
    int ret;

    switch (anterior) {
    case 2:
        ret = 3;
        break;
    case 3:
        ret = 2;
        break;
    default: /* first time */
        ret = 2;
        break;
    }

    anterior = ret;
    return ret;
}


int sch_ret = 0;
void *sch(void *arg) {
    int turno_local;

    while (1) {
        pthread_mutex_lock(&m_sch);
        
        while (turno != 1) {
            pthread_cond_wait(&SCH_GO, &m_sch);
        }
        printf("[THREAD SCH]\n");
        usleep(MS100);
        
        turno_local = sig_turno();
        turno = turno_local;
        switch (turno_local) {
        case 2:
            pthread_cond_signal(&THREAD2_GO);
            break;
        case 3:
            pthread_cond_signal(&THREAD3_GO);
            break;
        default:
            printf("error.\n");
            break;
        }
        pthread_mutex_unlock(&m_sch);
    }

    sch_ret = 1;
    return &sch_ret;
}


int thread2_ret = 0;
void *thread2(void *arg) {
    while (1) {
        pthread_mutex_lock(&m_sch);

        while (turno != 2) {
            pthread_cond_wait(&THREAD2_GO, &m_sch);
        }
        printf("[THREAD 2]\n");

        usleep(HALFS);

        turno = 1;

        pthread_cond_signal(&SCH_GO);
        pthread_mutex_unlock(&m_sch);
    }

    thread2_ret = 2;
    return &thread2_ret;
}

int thread3_ret = 0;
void *thread3(void *arg) {
    while (1) {
        pthread_mutex_lock(&m_sch);

        while (turno != 3) {
            pthread_cond_wait(&THREAD3_GO, &m_sch);
        }
        printf("[THREAD 3]\n");

        usleep(HALFS);

        turno = 1;

        pthread_cond_signal(&SCH_GO);
        pthread_mutex_unlock(&m_sch);
    }

    thread3_ret = 3;
    return &thread3_ret;
}

int main() {
    void *ret;

    pthread_t thread_sch, thread_2, thread_3;
    
    pthread_cond_init(&SCH_GO, NULL);
    pthread_cond_init(&THREAD2_GO, NULL);
    pthread_cond_init(&THREAD3_GO, NULL);
    pthread_mutex_init(&m_sch, NULL);

    pthread_create(&thread_sch, NULL, sch, NULL);
    usleep(MS100);
    pthread_create(&thread_2, NULL, thread2, NULL);
    pthread_create(&thread_3, NULL, thread3, NULL);


    pthread_join(thread_sch, &ret);
    pthread_join(thread_2, &ret);
    pthread_join(thread_3, &ret);
    printf("main() ending\n");

    return 0;
}

Where sch is the scheduler's thread.

This can be extended to more threads and works as expected, even though it's just a draft. I've been unable to obtain similar behaviour using only mutexes, so conditions have been the way to go.

I do accept criticism and improvements to this solution, I don't really know if it is correct/good practice.

Zya
  • 27
  • 6