0

I'm quite new to multi-threading programming. I want to write a code with 3 different threads, each of which prints some different characters. The execution of the threads should be based on a round-robin algorithm. Suppose we have t1, t2, and t3 threads, their execution sequence should be like t1, t2, t3, t1, t2, t3,...

I write the following code for this purpose:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sched.h>

void* a(void* ptr) {
    for (int i=0; i< 10; i++){
        printf("1");
        printf("2");
        printf("3");
        printf("4");
    }
    return NULL;
}

void* b(void* ptr) {
    for (int i=0; i< 10; i++){
        printf("a");
        printf("b");
        printf("c");
        printf("d");
    }
    return NULL;
}

void* c(void* ptr) {
    for (int i=0; i< 10; i++){
        printf("5");
        printf("6");
        printf("7");
        printf("8");
    }
    return NULL;
}

int main() {
    pthread_attr_t attr1, attr2, attr3;
    
    pthread_attr_init(&attr1);
    pthread_attr_init(&attr2);
    pthread_attr_init(&attr3);
    
    pthread_t t1, t2, t3;
    
    pthread_attr_setschedpolicy(&attr1, SCHED_RR);
    pthread_create(&t1, &attr1, a, NULL);
    
    pthread_attr_setschedpolicy(&attr2, SCHED_RR);
    pthread_create(&t2, &attr2, b, NULL);
    
    pthread_attr_setschedpolicy(&attr3, SCHED_RR);
    pthread_create(&t3, &attr3, c, NULL);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    
    pthread_exit(0);
    return 0;
}

And the output is:

First of all: the output is not reproducible, every time I run the binary file I get different output.

12341234123412341234156785678567856785678567856785678567856782341234123412341234abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd.

My desired output is something like this:

123abc567...

Does everybody know what the problem is with my code?

Thanks

Sooora
  • 171
  • 9
  • 1
    "Round Robin Scheduling" will not synchronize with your `for` loops. No form of implicit scheduling will synchronize with your loops. If you want the threads to take turns doing something, then you will need to write explicit code that lets each thread tell the next thread "it's your turn now." But,... – Solomon Slow Jan 07 '22 at 23:04
  • 2
    ...If the threads don't do anything except when it's their turn, then your program _effectively_ is single threaded. The whole point of threading is to let the threads run _independently_ of each other. It doesn't make sense to use threads when you want everything to be done in a certain sequence. The best way to get the output that you want would be to forget about threads, and replace the entire body of your `main()` routine with a loop: `while(1){a();b();c();}` – Solomon Slow Jan 07 '22 at 23:08
  • @SolomonSlow Thanks for your comment. You mean ```pthread_attr_setschedpolicy(&attr1, SCHED_RR);``` doesn't imply Round-robin scheduling? – Sooora Jan 07 '22 at 23:36
  • Maybe you are asking why the "1234" thread got to run twice before the "abcd" got to run. I don't know whether there's any guarantee that the threads will start executing in the same order as their `pthread_create(...)` calls. It may be that the "abcd" thread was the last to start. Also, are you running this code on a multi-CPU host? Because I would not expect SCHED_RR to cause the threads to strictly take turns if there is more than one CPU available to run them. – Solomon Slow Jan 07 '22 at 23:59
  • Or, are you asking why the output is not _exactly_ one iteration of `1234` followed by one of `abcd` followed by one of `5678`, and repeat forever? If so, the reason for that is, the SCHED_RR policy doesn't know the structure of your program. It doesn't know that your program contains `for` loops. It will preempt your threads any time one has been executing for more than a certain amount of _time._ Those preemptions could happen _anywhere_ within the loop, could happen at a different point in the loop each time the thread is preempted, and as you can see, they don't happen on every iteration. – Solomon Slow Jan 08 '22 at 00:16
  • @SolomonSlow Thanks for the explanation. What I want is the second case(one iteration of ```1234``` followed by one iteration of ```abcd``` followed by one iteration of ```5678``` and repeat). I unrolled the fo loops in the functions and run the program again, but I still don't get the reproducible result. Do you know the reason? How can I ensure that my threads are being executed in the Round-robin scheduling policy? – Sooora Jan 09 '22 at 17:19

1 Answers1

1

How can I ensure that my threads are being executed in the Round-robin scheduling policy?

You're almost there. You ensure it by calling pthread_attr_setschedpolicy(..., SCHED_RR); but then, you need to check the return value to see if it actually worked:

pthread_attr_t attr1;
int rVal = pthread_attr_setschedpolicy(&attr1, SCHED_RR);
if (rVal != 0) {
    // Failed to set the desired scheduler policy.
    perror("pthread_attr_setschedpolicy(&attr1, SCHED_RR)");
    exit(1);
}

rVal = pthread_create(&t1, &attr1, a, NULL);
if (rVal != 0) {
    // Failed to create a new thread.
    perror("pthread_create(&t1, &attr1, a, NULL)");
    exit(1);
}

What I want is...one iteration of 1234 followed by one iteration of abcd followed by one iteration of 5678 and repeat.

Well, that's the real problem. "Round-robin" does not mean what you seem to think it means. In fact, There is no scheduler policy that you can set that will make your program produce that output. If you want the threads to cooperate with each other in that way, then you'll need to write explicit code to make them communicate with each other. You need each thread's for loop to;

  1. Await a message/signal/event that means, "It's your turn,"
  2. Print it's thing,
  3. Send a message/signal/event to the next thread,
  4. Go back to step 1.

Then, after your main thread has created each of the three new threads, it needs to kick the whole thing off by telling any one of the new threads, "it's your turn."

I would use a semaphore as the means by which one thread tells another, "It's your turn." I would have one semaphore per thread, and I would pass pointers to two semaphores in to each thread: The one that the thread awaits to await its turn, and the one that the thread signals to let the next thread run.

Illustration of the relationship between the semaphores and the threads

Unfortunately, the Posix Threads Library (pthreads) doesn't provide any easy-to-use semaphore, and Unix semaphores aren't exactly trivial to use.

IMO, you should do a little research, and then come back and ask a new question if you encounter anything that you don't understand.

Solomon Slow
  • 25,130
  • 5
  • 37
  • 57