2

I'm currently learning about threads and my professor in class showed us this example attempting to demonstrate concurrency for threads

#include <iostream>

using namespace std;

void* threadHandler(void * val) {
    long long* a = (long long *) val;

    for (long long i = 0; i<100000; i++)
        (*a)++;
        
    int id = pthread_self();
    printf("thread : %d a = %d\n", id, *a);
    pthread_exit(nullptr);
}

int main() {
    long long a = 0;

    pthread_t t1, t2;

    pthread_setconcurrency(2);

    pthread_create(&t1, nullptr, threadHandler, &a);
    pthread_create(&t2, nullptr, threadHandler, &a);

    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);

    printf("main : a = %d\n", a);

    return 0;
}

Could anyone explain why the resulting value of a is not 200,000 as expected? The values are inconsistent, and always below 200,000.

enter image description here

However, when the incrementing loop is reduced to 10,000 , the result is as expected : a = 20,000.

enter image description here

Thanks.

Remi Jones
  • 45
  • 4
  • 1
    You have a race since you are not synchronizing the increments. Make the variable `atomic` or use a `mutex`. Also: Use the correct format specifiers for `printf`. – Ted Lyngmo Oct 09 '22 at 13:47
  • 1
    You're writing to the same variable from multiple threads without any form of synchronization. That's undefined behaviour. – G.M. Oct 09 '22 at 13:47
  • 1
    1st thread reads some value of `a`, lets say `5`. 2nd other thread reads `5` too. Both threads decide to write `6`. Different behavior from the 1st thread seeing `5`, incrementing to `6`, and then the 2nd thread seeing the `6` – asimes Oct 09 '22 at 13:50
  • 1
    Btw, why are you using the platform specific C API `pthread`? `std::thread` is part of standard C++ since C++11. – Ted Lyngmo Oct 09 '22 at 13:51
  • @TedLyngmo -- `atomic` or `mutex` might make the problem go away, but there is no requirement that they work correctly with thread mechanisms other than `std::thread`. – Pete Becker Oct 09 '22 at 19:06
  • @PeteBecker I'd use `pthread_mutex_t` if I used `pthread`, `mtx_t` if I used the standard C threads and `std::mutex` if I used C++ `std::thread` (or the `atomic` available in each version). – Ted Lyngmo Oct 09 '22 at 19:21

1 Answers1

1
pthread_t t1, t2;

Your professor is showing you historical POSIX thread API, that was mostly used in ancient times, back when dinosaurs roamed the Earth mostly armed with C (C++ has not been invented yet). Modern C++ uses std::threads to implement multiple execution threads. However, whether it's POSIX threads, or modern C++'s std::thread, the underlying fundamental problem is the same:

   (*a)++;

The object referenced here (a counter, in this case) is getting modified by multiple execution threads.

In C++ when an object is accessed by multiple threads, and that access includes at least one execution thread modifying the object (not even all of them), every access to the object must be synchronized. You can't just modify an object, in multiple execution threads, just like that. Interthread synchronization in itself is a complex topic, with many rules, that involves mutexes and condition variables, that cannot be summarized in just one or two sentences except to mention that the term "synchronization" refers to changes to the shared object made by one execution thread getting "synchronized" to other execution threads. Hopefully your class uses a good C++ textbook that offers a full and thorough discussion of thread synchronization, where I'll refer you for complete information on this topic.

Improper thread synchronization results in undefined behavior. The results you get are, therefore, completely nonsensical, and may actually vary every time you run your program.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148