views:

264

answers:

2

I've been using lock on value type properties when multi-threaded access is required. Also, I've been meaning to become more diligent about applying proper access modifiers, especially in my library code that is starting to become useful in multiple projects. I've written some code and would like to request comments on the various strategies in it for properties and locking the member variables they wrap. Thanks.

using System;

public class Program
{
    static void Main(string[] args)
    {
        SomeValueType svt = new SomeValueType();
        SomeReferenceType srt = new SomeReferenceType();        
        PermissionsAndLocking p = new PermissionsAndLocking(5, svt, srt);

        //Invalid.
        //p.X = 6;

        //Invalid
        //p.Svt = new SomeValueType();
        //Invalid
        //p.Svt.X = 1;
        //Valid, but changes a copy of p.Svt because Svt is a value type.
        SomeValueType svt2 = p.Svt;
        svt2.X = 7;

        //Invalid
        //p.Srt = new SomeReferenceType();
        //Valid, change the member data of p.Srt.
        p.Srt.X = 8;        
        SomeReferenceType srt2 = p.Srt;
        srt2.X = 9;

        Console.WriteLine("Press the any key.");
        Console.Read();
    }
}

public class PermissionsAndLocking
{
    //_x cannot be changed outside the class.
    //_x cannot be changed "at the same time" it is being accessed???
    private readonly object _xLock = new object();
    private int _x;
    public int X
    {
        get
        {
            lock (_xLock)
            {
                return _x;
            }
        }
        private set
        {
            lock (_xLock)
            {
                _x = value;
            }
        }
    }

    //_svt and its members cannot be assigned to outside the class.
    //_svt cannot be changed "at the same time as" it is being accessed.
    private readonly object _svtLock = new object();
    private SomeValueType _svt;
    public SomeValueType Svt
    {
        get
        {
            lock (_svtLock)
            {
                return _svt;
            }
        }
        private set
        {
            lock (_svtLock)
            {
                _svt = value;
            }
        }
    }

    //private on set works for = but member data can still be manipulated...
    //Locking isn't complete because the reference is returned and can be accessed at a later time???
    private readonly object _srtLock = new object();
    private SomeReferenceType _srt;
    public SomeReferenceType Srt
    {
        get
        {
            lock (_srtLock)
            {
                return _srt;
            }
        }
        private set
        {
            lock (_srtLock)
            {
                _srt = value;
            }
        }
    }

    public PermissionsAndLocking(int x, SomeValueType svt, SomeReferenceType srt)
    {
        _x = x;
        _svt = svt;
        _srt = srt;
    }
}

public struct SomeValueType
{
    public int X;
}

public class SomeReferenceType
{
    public int X;
}
A: 
  1. You should always lock a static object, so you should mark _svtLock as static in order for the lock to have an effect.
  2. _x cannot be changed outside the class. True. It must be changed via X.
  3. If you implement lock correctly ( see 1), then _x can't be changed at the time it's accessed.
Ngu Soon Hui
So a lock object should always be static? What if I have a non-static class and I wish to only lock access to member data within that object. I don't want to tie up all the objects of that type because they are waiting for the lock to free up.
Nate
@Ngu -1 for several errors. Just because an object is static is not sufficient to require a lock. private static readonly int pi = 3.1415; No need to lock this, right? 2. Implementing lock "correctly", does not imply that the shared memory (_x) cannot be changed. In fact, it does nothing at all to prevent the variable from being changed. only if every other code block that might change _x is also inside of a lock block that uses the exact same lock reference is this assured.
Charles Bretana
+1  A: 

You need to read about multi-threading and concurrency. Locking is about protecting invariants whilst they are invalid, i.e., while an invariant is invalid, prevent concurrent access to the shared memory that the invariant is dependant upon. The first step is to understand what invariant your code routine has, and secondly, within which block of code is the invariant invalid.

For example, a property getter has no intrinsic need to be synchronized with a lock. It only reads the property value. What invariant is invalid while this read is going on ? An operation that reads the variable, increments it, and then writes the incremented value back to the property might need to be locked, but locking the individual getter and setter would be totally inadequate. The entire operataion, inc;uding the read and the write, would have to be inside the protected block.

Charles Bretana
Indeed, in this case I have data one thread is changing and another is accessing. The data is made available via properties. I was looking at whether or not locking could be used to coordinate access.
Nate
Good points on the second paragraph. Somewhat like the Interlocked class with the Compare/Exchange methods. Does it in one pass, all locked. So, knowing this, would you say locking and properties just don't mix well. Is there a way to do it right with properties?
Nate
@Nate, No, ther eis no way. Multi-threading and concurrency is way too complex to allow the sort of "one-size fits all" approach that close to the metal. You HAVE to analyze each functional operation you are coding to determine whether a vulnerable piece of shared memory is exposed. And the appropriate mechanism to fix the issue is almost always heavily dependant on the scenario, and is seldom trivial.
Charles Bretana