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.