cubuntupthreadspthread-join

Functionality of pthread_join() in Ubuntu C


Working on a small C code with pthread library. My requirement for this code is to first deposit money then withdraw money.

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

int counter = 0;
void *increase();
void *decrease();

int balance = 500;

void *deposit(void*);
void *withdraw(void*);


int main() {

    pthread_t t_inc, t_dec;
    int dep_money = 300;
    int wd_money = 100;

    pthread_create(&t_inc, NULL, deposit, &dep_money);
    pthread_create(&t_dec, NULL, withdraw, &wd_money);

    // Wait until the thread is finished
    pthread_join(t_inc, NULL);
    pthread_join(t_dec, NULL);
    }
    return 0;
}

// Functions for thread
void *deposit(void *args) {
    int *money = (int *)args;
    balance += *money;
    printf("Deposit: New balance: %d\n", balance);
    return NULL;
}

void *withdraw(void *args) {
    int *money = (int *)args;

    if (balance < *money) {
        printf("Not enough balance\n");
    } else {
        balance -= *money;
        printf("Withdraw: New balance: %d\n", balance);
    }
    return NULL;
}

I have used this code on 2 different operating systems, Ubuntu & macOS. I have got for some reason 2 different results.

For macOS:

Deposit: New balance: 800

Withdraw: New balance: 700

For Ubuntu:

Withdraw: New balance: 400

Deposit: New balance: 700

But when I change the order of pthread_join() in Ubuntu it worked as the order I wanted it.

pthread_create(&t_inc, NULL, deposit, &dep_money);
pthread_join(t_inc, NULL);

pthread_create(&t_dec, NULL, withdraw, &w_money);
pthread_join(t_dec, NULL);

In conclusion, my question is why in the first code run in Ubuntu it didn't run as the first order of the pthread_join(t_inc, NULL); then the second pthread_join(t_dec, NULL); instead it runs the opposite? And I had to call pthread_join() after pthread_create() immediately for it to work in order which I don't think it's efficient since I didn't create the second thread yet.


Solution

  • That's just a race condition. Calling pthread_create of one thread before that of the other does not guarantee that the second thread shall be scheduled by the OS after the first. So calling

    pthread_create(&t_inc, NULL, deposit, &dep_money);
    pthread_create(&t_dec, NULL, withdraw, &wd_money);
    

    doesn't mean that you'll invoke the thread running deposit() before the one running withdraw(). It's totally up to the OS on when they are actually scheduled and as a programmer, one should not make any assumptions about it. Hence the discrepancies in the result because any one of the thread might run before.

    So, to answer your question

    my question is why in the first code run in Ubuntu it didn't run as the first order of the pthread_join(t_inc, NULL); then the second pthread_join(t_dec, NULL); instead it runs the opposite?

    Don't have any expectations about the order in which threads are actually run.

    Calling

    pthread_create(&t_inc, NULL, deposit, &dep_money);
    pthread_join(t_inc, NULL);
    
    pthread_create(&t_dec, NULL, withdraw, &w_money);
    pthread_join(t_dec, NULL);
    

    will always work since you have essentially serialized the thread creation and the thread calling deposit() shall always complete before the one running withdraw() is spawned and thus it works on all OSs.

    EDIT:

    For completeness, as you mentioned, creating threads and immediately calling join() isn't really of much use since it is as good as just calling the functions deposit() and withdraw() in order, given that here deposit() and withdraw() don't really do much significant work that might stall the main thread, blocking it to proceed to do other tasks. Also, you would usually need to take care of shared variables. e.g. If you just had a single variable amount and then each thread updated the same value, one adding balance and another deducting it, then you could be possibly needing constructs like mutex/condition_variable to achieve your desired results, free of race conditions.