Alright, so you're using an atomic version counter to make sure only the latest network request's result gets used. Each button click bumps up the version, and when the request finishes, it checks if its version is still the latest one. If not, it discards the result. Solid approach.
std::atomic<int> version{0};
// Button Click handler
void OnClick() {
auto v = version.fetch_add(1, std::memory_order_acq_rel); // Increment version
std::thread([v]() {
int current = v + 1;
auto r = network_request(current); // Perform network request
if (version.compare_exchange_strong(current, current, std::memory_order_release, std::memory_order_relaxed)) {
use_req(r); // Use the result if version matches
}
}).detach();
}
You asked whether the CAS (compare_exchange_strong) could just be a load operation. Short answer: No. Here’s why:
A simple load() would just read the current version, but it wouldn’t prevent a race condition where another thread increments the version between the time you check it and when you act on it.
compare_exchange_strong() is an atomic read-modify-write operation. It ensures that the version hasn't changed since the request started. If another request has already started in the meantime (i.e., another button click happened), the CAS check fails, and the outdated result is discarded.
Let's say you click the button twice:
First click → version starts at 0, fetch_add makes it 1.
Second click → version becomes 2.
The first request finishes and checks if version is still 1. But nope, it's 2 now. So the first result is ignored.
The second request finishes, sees that version is still 2, and its result gets used.
If we replaced CAS with just a load, we wouldn’t get this guarantee. The first request might read the version at 1 before fetch_add in the second click updates it, and it might wrongly think it should use its result.
memory_order_acq_rel for fetch_add() ensures that updates to version are visible across threads.
memory_order_release in CAS ensures that if the update succeeds, all prior writes (like the network request’s result) are properly published before any other thread reads them.
memory_order_relaxed for the failure case is fine since we don’t need extra synchronization there—we just need to check the version.
Yes, CAS is necessary here. A plain load wouldn’t catch race conditions where the version changes between request start and completion. CAS ensures that only the result of the latest request gets used. Keep it as is. I spent quite some time on this answer as I desperately need that reputation to get the ability to comment on other's questions lol.