Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why magic does an locking an instance of System.Object allow differently than locking a specific instance type?

I have been learning about locking on threads and I have not found an explanation for why creating a typical System.Object, locking it and carrying out whatever actions are required during the lock provides the thread safety?

Example

object obj = new object()
lock (obj) {
  //code here
}

At first I thought that it was just being used as a place holder in examples and meant to be swapped out with the Type you are dealing with. But I find examples such as Dennis Phillips points out, doesn't appear to be anything different than actually using an instance of Object.

So taking an example of needing to update a private dictionary, what does locking an instance of System.Object do to provide thread safety as opposed to actually locking the dictionary (I know locking the dictionary in this case could case synchronization issues)? What if the dictionary was public?

//what if this was public?
private Dictionary<string, string> someDict = new Dictionary<string, string>();

var obj = new Object();
lock (obj) {
  //do something with the dictionary
}
like image 345
pghtech Avatar asked Jan 11 '12 14:01

pghtech


3 Answers

The lock itself provides no safety whatsoever for the Dictionary<TKey, TValue> type. What a lock does is essentially

For every use of lock(objInstance) only one thread will ever be in the body of the lock statement for a given object (objInstance)

If every use of a given Dictionary<TKey, TValue> instance occurs inside a lock. And every one of those lock uses the same object then you know that only one thread at a time is ever accessing / modifying the dictionary. This is critical to preventing multiple threads from reading and writing to it at the same time and corrupting its internal state.

There is one giant problem with this approach though: You have to make sure every use of the dictionary occurs inside a lock and it uses the same object. If you forget even one then you've created a potential race condition, there will be no compiler warnings and likely the bug will remain undiscovered for some time.

In the second sample you showed you're using a local object instance (var indicates a method local) as a lock parameter for an object field. This is almost certainly the wrong thing to do. The local will live only for the lifetime of the method. Hence 2 calls to the method will use lock on different locals and hence all methods will be able to simultaneously enter the lock.

like image 177
JaredPar Avatar answered Jan 03 '23 17:01

JaredPar


It used to be common practice to lock on the shared data itself:

private Dictionary<string, string> someDict = new Dictionary<string, string>();

lock (someDict ) 
{
  //do something with the dictionary
}

But the (somewhat theoretical) objection is that other code, outside of your control, could also lock on someDict and then you might have a deadlock.

So it is recommended to use a (very) private object, declared in 1-to-1 correspondence with the data, to use as a stand-in for the lock. As long as all code that accesses the dictionary locks on on obj the tread-safety is guaranteed.

// the following 2 lines belong together!!
private Dictionary<string, string> someDict = new Dictionary<string, string>();
private object obj = new Object();


// multiple code segments like this
lock (obj) 
{
  //do something with the dictionary
}

So the purpose of obj is to act as a proxy for the dictionary, and since its Type doesn't matter we use the simplest type, System.Object.

What if the dictionary was public?

Then all bets are off, any code could access the Dictionary and code outside the containing class is not even able to lock on the guard object. And before you start looking for fixes, that simply is not an sustainable pattern. Use a ConcurrentDictionary or keep a normal one private.

like image 35
Henk Holterman Avatar answered Jan 03 '23 17:01

Henk Holterman


The object which is used for locking does not stand in relation to the objects that are modified during the lock. It could be anything, but should be private and no string, as public objects could be modified externally and strings could be used by two locks by mistake.

like image 25
MatthiasG Avatar answered Jan 03 '23 17:01

MatthiasG