5
votes

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...

1
At what point in time do you think it might have a cached null floating around? - Marc Gravell
In addition to Marc: the _padLock ref doesn't change so caching is irrelevant. The first read will happen after it is set. Your question would have more merit if it was create-on-demand or something. - Henk Holterman
Starting a thread is in itself enough to force caches to be updated. You'll have to come up with a better example. - Hans Passant
@Marc, Henk - the ctor's thread can assign the synclock into its cache, not showing up in the main memory, which ConcurrentMethod's thread may then read - Ohad Schneider
It is no different. Waking up a tp thread still involves an internal synchronization that syncs the caches. So does any thread context switch. - Hans Passant

1 Answers

10
votes

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.