Bab64
Bab64

Reputation: 109

C++ std::atomic - impossibility of synchronizing 2 threads based on a shared atomic variable

In my program, 2 threads sort a simple array in a contradictory (for test) way. The single idea is to prevent both threads to sort the array at the same time using std::atomic. The expected result is that the array will be sorted, either descending or ascending depending on "the winner thread". I know that this can easily be solved with mutexes, but I just here try to understand the benefit of std::atomic. My program fails in 10% of all execution cases: failure is when both sorts intertwine... Thanks in advance for any highlighting...

    std::array<int, 5> my_array{ 1, 4, 3, 2, 5 };
    std::atomic< std::array<int, 5>* > my_array_{ &my_array };

    std::thread my_thread(
        [&my_array_]() {
            std::array<int, 5>* my_array = nullptr;
            do { my_array = my_array_.load(std::memory_order_acquire); } while (my_array == nullptr);
            my_array_.store(nullptr, std::memory_order_release);

            std::sort(my_array->begin(), my_array->end(),
                [](const int a, const  int b) {
                    // std::cout << "\tascending a: " << a << " b: " << b << std::endl;
                    return a > b;
                });
            my_array_.store(my_array, std::memory_order_release);
        });

    std::thread my_thread_(
        [&my_array_]() {
            std::array<int, 5>* my_array = nullptr;
            do { my_array = my_array_.load(std::memory_order_acquire); } while (my_array == nullptr);
            my_array_.store(nullptr, std::memory_order_release);

            std::sort(my_array->begin(), my_array->end(),
                [](const int a, const int b) {
                    // std::cout << "\tdescending a: " << a << " b: " << b << std::endl;
                    return a < b;
                });
            my_array_.store(my_array, std::memory_order_release);
        });

    my_thread_.join();
    my_thread.join();

    for (const int i : my_array)
        std::cout << "\t" << i;

Upvotes: 0

Views: 175

Answers (2)

Bab64
Bab64

Reputation: 109

According to Anthony's remarks, here is a solution (some changes in variable naming for better distinction):

std::array<int, 5> my_array{ 1, 4, 3, 2, 5 };
std::atomic< std::array<int, 5>* > my_atomic{ &my_array };

    std::thread ascending(
        [&my_atomic]() {
            // Resource is unavailable:
            std::array<int, 5>* my_array = nullptr;
            // Race condition, try to be the first to get the resource:
            do { my_array = my_atomic.exchange(nullptr); } while (my_array == nullptr);
            std::sort(my_array->begin(), my_array->end(),
                [](const int a, const int b) {
                    // std::cout << "\tascending a: " << a << " b: " << b << std::endl;
                    return a > b;
                });
            // Release resource:
            my_atomic.exchange(my_array);
        });

    std::thread descending(
        [&my_atomic]() {
            // Resource is unavailable:
            std::array<int, 5>* my_array = nullptr;
            // Race condition, try to be the first to get the resource:
            do { my_array = my_atomic.exchange(nullptr); } while (my_array == nullptr);
            std::sort(my_array->begin(), my_array->end(),
                [](const int a, const int b) {
                    // std::cout << "\tdescending a: " << a << " b: " << b << std::endl;
                    return a < b;
                });
            // Release resource:
            my_atomic.exchange(my_array);
        });

    descending.join();
    ascending.join();

    for (const int i : my_array) // Either ascending or descending based on the "loser thread"
        std::cout << "\t" << i;```

Upvotes: 0

Anthony Williams
Anthony Williams

Reputation: 68581

If you want to use the my_array_ atomic variable to indicate whether some thread has claimed it, then you can simply call

auto local_array=my_array_.exchange(nullptr)

from your threads. local_array will thus be a pointer to my_array if this is the first thread to do the exchange, and nullptr if the other thread got there first.

As it stands, with the plain loads and stores, then both threads can read the my_array_ pointer and see that it is not nullptr, then both can store nullptr in it, and both think they got there first.

Also: Using my_array for the name of the global, and the name of the local pointer is asking for confusion.

Upvotes: 3

Related Questions