Home > Blockchain >  Order of execution of print with pthreads in Linux
Order of execution of print with pthreads in Linux

Time:09-26

I'm using C and I want to get the String "ABCABCABCABCABCABC" in the output screen through multithreading. One thread displays the 'A' character, the second one displays the 'B' and the third one displays the 'C'. If I compile the following code:

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

#define cantidad_impresion_letra 6
pthread_mutex_t semaforo = PTHREAD_MUTEX_INITIALIZER;

void *escribirA(void *unused){
    int i;
    for(i=0;i<cantidad_impresion_letra;i  ){
        pthread_mutex_lock(&semaforo);
        printf("A");
        pthread_mutex_unlock(&semaforo);
    }
}

void *escribirB(void *unused){
    int i;
    for(i=0;i<cantidad_impresion_letra;i  ){
        pthread_mutex_lock(&semaforo);
        printf("B");
        pthread_mutex_unlock(&semaforo);
    }
}

void *escribirC(void *unused){
    int i;
    for(i=0;i<cantidad_impresion_letra;i  ){
        pthread_mutex_lock(&semaforo);
        printf("C");
        pthread_mutex_unlock(&semaforo);
    }
}

int main(){
    pthread_t thread1, thread2, thread3;
    
    pthread_create(&thread1,NULL,escribirA,NULL);
    pthread_create(&thread2,NULL,escribirB,NULL);
    pthread_create(&thread3,NULL,escribirC,NULL);
        
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    pthread_join(thread3, NULL);
    
    return(0);
}

On Windows through Dev-C , the console throws me: ABACBACBACBACBACBC but if I compile the same code in Linux, I get CCCCCCBBBBBBAAAAAA.
Can someone explain me this please?

CodePudding user response:

Can someone explain me this please?

The different output in linux has to do with the used scheduler.

man sched

Since Linux 2.6.23, the default scheduler is CFS, the "Completely Fair Scheduler".

Completely Fair Scheduler:

Each per-CPU run-queue of type cfs_rq sorts sched_entity structures in a time-ordered fashion into a red-black tree (or 'rbtree' in Linux lingo), where the leftmost node is occupied by the entity that has received the least slice of execution time (which is saved in the vruntime field of the entity). The nodes are indexed by processor "execution time" in nanoseconds.

What does that mean:

Each thread runs so fast, that the scheduler doesn't deem it necessary to perform a context switch.

There are only 6 iteration steps and an output into a buffered stream (no newline, therefore no output to the terminal until a flush occurs).

Do the test, increase the iteration steps, add more characters to the output and add a newline character (\n).

The sequence will start to change.

On my system, the sequence started to change with 200 iteration steps and 3 characters (plus \n).


I'm using C and I want to get the String "ABCABCABCABCABCABC"

1. Sequential output is best achieved through sequential execution.

  • use only one thread (single threaded)

2. Notify/Wait

  • each thread performs an iteration, signals the other thread and then waits for a signal to arrive in order to continue on the next cycle

3. Barrier

//introduce NUM_THREAD lists
char dataA[cantidad_impresion_letra];
char dataB[cantidad_impresion_letra];
char dataC[cantidad_impresion_letra];

//let the thread do their work without any disturbance
void *escribirA(void *unused){ //unused could be used to pass the lists
    int i;
    for(i=0;i<cantidad_impresion_letra;i  ){
        //pthread_mutex_lock(&semaforo); //not needed
        //instead of printf("A");
        dataA[i] = 'A';
        //pthread_mutex_unlock(&semaforo); //not needed
    }
}

//same for escribirB but write into dataB
//same for escribirC but write into dataC

int main()
{
    //...
    //after joining the threads
    //collective output in any desired order
    for (int i=0; i < cantidad_impresion_letra;   i) {
        printf("%c", dataA[i]);
        printf("%c", dataB[i]);
        printf("%c", dataC[i]);
    }
    return 0;
}

The advantages:

  • no lock/unlock (if the global lists are not accessed otherwise)
  • no waiting and broadcasting
  • guaranteed synchronized output
  • let the OS decide when it is best to yield a thread (scheduler)

Disadvantage:

  • more memory intensive (depends on the number of threads and the iteration steps)

CodePudding user response:

You can store the order of execution in a global volatile variable named for example order which is set with the values 0, 1 and 2 circularly (thanks to an incrementation and a modulo operation). This variable is checked with a condition variable:

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

volatile int order = 0;

#define cantidad_impresion_letra 6
pthread_mutex_t semaforo = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;


void *escribirA(void *unused){
    int i;
    for(i=0;i<cantidad_impresion_letra;i  ){
        pthread_mutex_lock(&semaforo);
        while (order != 0) {
          // Wake up another thread before going to sleep
          pthread_cond_signal(&cond);
          pthread_cond_wait(&cond, &semaforo);
        }
        printf("A");
        order = (order   1) % 3;
        fflush(stdout);
        // Wake up another thread
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&semaforo);
    }
}

void *escribirB(void *unused){
    int i;
    for(i=0;i<cantidad_impresion_letra;i  ){
        pthread_mutex_lock(&semaforo);
        while (order != 1) {
          // Wake up another thread before going to sleep
          pthread_cond_signal(&cond);
          pthread_cond_wait(&cond, &semaforo);
        }
        printf("B");
        order = (order   1) % 3;
        fflush(stdout);
        // Wake up another thread
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&semaforo);
    }
}

void *escribirC(void *unused){
    int i;
    for(i=0;i<cantidad_impresion_letra;i  ){
        pthread_mutex_lock(&semaforo);
        while (order != 2) {
          // Wake up another thread before going to sleep
          pthread_cond_signal(&cond);
          pthread_cond_wait(&cond, &semaforo);
        }
        printf("C");
        order = (order   1) % 3;
        fflush(stdout);
        // Wake up another thread
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&semaforo);
    }
}

int main(){
    pthread_t thread1, thread2, thread3;
    
    pthread_create(&thread1,NULL,escribirA,NULL);
    pthread_create(&thread2,NULL,escribirB,NULL);
    pthread_create(&thread3,NULL,escribirC,NULL);
        
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    pthread_join(thread3, NULL);
    
    return(0);
}

Example of execution:

$ gcc order.c -o order -lpthread
$ ./order
ABCABCABCABCABCABC

CodePudding user response:

On Windows through Dev-C , the console throws me: ABACBACBACBACBACBC but if I compile the same code in Linux, I get CCCCCCBBBBBBAAAAAA. Can someone explain me this please?

The Linux behavior is what you should expect. Some thread has to start running first. Whichever thread it is, the other two threads will be blocked. That thread will be able to continue running for some time since there is nothing that it needs to wait for.

The behavior you see in Windows is awful -- the worst possible. Either it's keeping all three threads scheduled or it's not. If it's keeping all three threads scheduled, it's using three cores for work that can be done equally fast with just one core. If it's not keeping all three threads scheduled, then you are getting one context switch per character of output and wasting CPU time constantly populating the code and data caches because you just switched from another task.

  • Related