I am not an expert in memory models, but now I think we have these guarantees.
As stated by Servy , the Wait
and Release
methods use Monitor
under the hood. However, << 22> may not be enough .
At the end of the Wait
method, just before the call to Monitor.Exit
, the volatile field is reduced.
if (lockTaken) { m_waitCount--; //m_waitCount is volatile Monitor.Exit(m_lockObj); }
As I understand it, the decrement operator used in the volatile field introduces the acquire and release operations, blocking the following instructions from reordering to it.
As for the Release
method, the situation is similar. In the beginning, we have both a lock collection and an unstable read / write operation.
lock (m_lockObj) { //m_currentCount is volatile if (m_maxCount - m_currentCount < releaseCount) { throw new SemaphoreFullException(); } m_currentCount += releaseCount;
Special thanks to Joe Duffy for pointing out the importance of mutable fields in SemaphoreSlim
.
EDIT . An example that demonstrates a situation where interlocks alone (without additional volatile operations) may be insufficient.
// Semaphore.Wait() lock (syncRoot) { // (1) // acquire semaphore } // end of Semaphore.Wait() // the critical section guarded by the 'semaphore lock' (2) // Semaphore.Release() lock (syncRoot) { // release semaphore } // end of Semaphore.Release()
The read instruction from critical section (2)
can be reordered to (1)
when the semaphore has not yet been received (another thread can still work in the critical section).
source share