C
C#4d ago
yeon

Mixing `Volatile` with `Interlocked`

I believe this is safe to do so, but I'm asking this just in case.
private int refreshing = 0;

private void RefreshLobbies()
{
// Skip if already refreshing
if (Interlocked.CompareExchange(ref this.refreshing, 1, 0) != 0)
return;

/* Do some work here... */

// Refreshing done, clear the flag
Volatile.Write(ref this.refreshing, 0);
}
private int refreshing = 0;

private void RefreshLobbies()
{
// Skip if already refreshing
if (Interlocked.CompareExchange(ref this.refreshing, 1, 0) != 0)
return;

/* Do some work here... */

// Refreshing done, clear the flag
Volatile.Write(ref this.refreshing, 0);
}
Can you mix Interlocked.CompareExchange with Volatile.Write like this? I believe both would perform the atomic operations, so it should be safe to do so in this pattern. Another question is whether the Interlocked.CompareExchange synchronizes-with Volatile.Write to create a happens-before relationship. Which means, in C++ terms, if the Interlocked.CompareExchange returns 0, previous memory operations in /* Do some work here... */ should be also visible to the current thread.
11 Replies
reflectronic
reflectronic4d ago
yes, it is fine to mix them like that i am not sure what you mean by "if Interlocked.CompareExchange returns zero, the memory operations should be visible." if it returns zero, the memory operations haven't happened yet you mean, if you call RefreshLobbies twice in a row?
yeon
yeonOP4d ago
Yes, for example two different threads calling RefreshLobbies() in a row When both falls to the /* Do some work here... */ part, one of them should have happened first, so the result of the previous thread should be visible to the future thread
reflectronic
reflectronic4d ago
the compare exchange and volatile write ensures the memory operations do not fall outside of the lock
yeon
yeonOP4d ago
In other words, the thread that come next the first thread won't see the stale state before the first thread did something
reflectronic
reflectronic4d ago
all of the memory operations will complete before the flag is cleared the volatile is a release, the interlocked is a full barrier. so nothing can move outside yes, your lock looks fine
yeon
yeonOP4d ago
Yeah, looks like that's the case. I was being pedantic because the msdn docs never mentions the memory ordering of Interlocked Thanks for the help
reflectronic
reflectronic4d ago
it is described in the memory model https://github.com/dotnet/runtime/blob/main/docs/design/specs/Memory-model.md all of the interlocked methods are a full barier
yeon
yeonOP4d ago
Nice, thanks for clarifying
SleepWellPupper
Piggybacking off of the question: Does int require a volatile write operation here at all? My understanding is that I would prefer volatile operations to prevent struct tearing, but int is written atomically. Is this correct?
reflectronic
reflectronic3d ago
the volatile is not about the atomicity it is about the ordering of memory operations the CPU and the compiler are allowed to execute memory reads and writes in a different order than the one which you wrote in the program this code is implementing a lock. the write releases the lock. if the CPU or compiler chooses to take some of the code that yeon wrote “inside” of the lock and execute it after that release, you have a broken lock. someone could see the lock is released, and enter the lock, but the other thread is still editing the protected data structures a Volatile write has “release semantics,” it means that any operations which were written before the write in the code must stay before the write. it is a useful property for… releasing a lock
SleepWellPupper
That makes sense, thanks for the explanation!

Did you find this page helpful?