Given a scenario where there's a function that should only be executed by one thread at any given time, and the rest just return (since a specific state is already being worked on), what's the best way to accomplish this?
public void RunOnce()
{
if(Interlocked.Exchange(ref m_isRunning, 1) == 1)
return;
// Run code that should only be executed once
// What mechanism do we use here to ensure thread safety?
Volatile.Write(ref m_isRunning, 0);
}
Would the same mechanism apply if m_isRunning is a state (ie. an integer representing an enum)?
The code in your question is thread-safe IMHO, but in general
the Interlocked.CompareExchange
method is more flexible than the Interlocked.Exchange
for implementing lock-free multithreading. Here is how I would prefer to code the RunOnce
method:
int _lock; // 0: not acquired, 1: acquired
public void RunOnce()
{
bool lockTaken = Interlocked.CompareExchange(ref _lock, 1, 0) == 0;
if (!lockTaken) return;
try
{
// Run code that should be executed by one thread only.
}
finally
{
bool lockReleased = Interlocked.CompareExchange(ref _lock, 0, 1) == 1;
if (!lockReleased)
throw new InvalidOperationException("Could not release the lock.");
}
}
My suggestion though would be to use the Monitor
class:
object _locker = new();
public void RunOnce()
{
bool lockTaken = Monitor.TryEnter(_locker);
if (!lockTaken) return;
try
{
// Run code that should be executed by one thread only.
}
finally { Monitor.Exit(_locker); }
}
...or the SemaphoreSlim
class if you prefer to prevent reentrancy:
SemaphoreSlim _semaphore = new(1, 1);
public void RunOnce()
{
bool lockTaken = _semaphore.Wait(0);
if (!lockTaken) return;
try
{
// Run code that should be executed by one thread only.
}
finally { _semaphore.Release(); }
}
It makes the intentions of your code cleaner IMHO.