Reputation: 109
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
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
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