By lock helpers I am referring to disposable objects with which locking can be implemented via using
statements. For example, consider a typical usage of the SyncLock
class from Jon Skeet's MiscUtil:
public class Example
{
private readonly SyncLock _padlock;
public Example()
{
_padlock = new SyncLock();
}
public void ConcurrentMethod()
{
using (_padlock.Lock())
{
// Now own the padlock - do concurrent stuff
}
}
}
Now, consider the following usage:
var example = new Example();
new Thread(example.ConcurrentMethod).Start();
My question is this - since example
is created on one thread and ConcurrentMethod
is called on another, couldn't ConcurrentMethod
's thread be oblivious to _padock
's assignment in the constructor (due to thread caching / read-write reordering), and thus throw a NullReferenceException
(on _padLock
itself) ?
I know that locking with Monitor
/lock
has the benefit of memory barriers, but when using lock helpers such as these I can't see why such barriers are guaranteed. In that case, as far as I understand, the constructor would have to be modified:
public Example()
{
_padlock = new SyncLock();
Thread.MemoryBarrier();
}
Source: Understanding the Impact of Low-Lock Techniques in Multithreaded Apps
EDIT Hans Passant suggests that the creation of a thread implies a memory barrier. So how about:
var example = new Example();
ThreadPool.QueueUserWorkItem(s => example.ConcurrentMethod());
Now a thread is not necessarily created...
Memory barrier is implemented by the hardware processor. CPUs with different architectures have different memory barrier instructions. Therefore, the programmer needs to explicitly call memory barrier in the code to solve the preceding problem.
In computing, a memory barrier, also known as a membar, memory fence or fence instruction, is a type of barrier instruction that causes a central processing unit (CPU) or compiler to enforce an ordering constraint on memory operations issued before and after the barrier instruction.
Fencing is the process of isolating a node of a computer cluster or protecting shared resources when a node appears to be malfunctioning.
No, you do not need to do anything special to guarentee that memory barriers are created. This is because almost any mechanism used to get a method executing on another thread produces a release-fence barrier on the calling thread and an aquire-fence barrier on the worker thread (actually they may be full fence barriers). So either QueueUserWorkItem
or Thread.Start
will automatically insert the necessary barriers. Your code is safe.
Also, as a matter of tangential interest Thread.Sleep
also generates a memory barrier. This is interesting because some people naively use Thread.Sleep
to simulate thread interleaving. If this strategy were used to troubleshoot low-lock code then it could very well mask the problem you were trying to find.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With