C# doesn\'t allow locking on a null value. I suppose I could check whether the value is null or not before I lock it, but because I haven\'t locked it another thread could c
Lock on a value that is never null, e.g.
Object _lockOnMe = new Object();
Object _iMightBeNull;
public void DoSomeKungFu() {
if (_iMightBeNull == null) {
lock (_lockOnMe) {
if (_iMightBeNull == null) {
_iMightBeNull = ... whatever ...;
}
}
}
}
Also be careful to avoid this interesting race condition with double-checked locking: Memory Model Guarantees in Double-checked Locking
There are two issues here:
First, don't lock on a null
object. It doesn't make sense as how can two objects, both null
, be differentiated?
Second, to safely initialise a variable in a multithreaded environment, use the double-checked locking pattern:
if (o == null) {
lock (lockObj) {
if (o == null) {
o = new Object();
}
}
}
This will ensure that another thread has not already initialised the object and can be used to implement the Singleton pattern.
You cannot lock on a null value because the CLR has no place to attach the SyncBlock to, which is what allows the CLR to synchronize access to arbitrary objects via Monitor.Enter/Exit (which is what lock
uses internally)
Why doesn't C# allow a null value to be locked?
Paul's answer is the only technically correct one so far so I would accept that one. It is because monitors in .NET use the sync block which is attached to all reference types. If you have a variable that is null
then it is not referring to any object and that means the monitor does not have access to a usable sync block.
How can I avoid this race condition?
The traditional approach is to lock on an object reference which you know will never be null
. If you find yourself in a situation where this cannot be guaranteed then I would classify this a non-traditional approach. There really is not much more I can mention here unless you describe in more detail the particular scenario that can lead to nullable lock targets.
First part of your question is already answered but I would like to add something for second part of your question.
It is simpler to use a different object to perform the locking, specially in this condition. This also resolve the issue to maintain the states of multiple shared objects in a critical section, e.g. list of employee and list of employee photos.
Moreover this technique is also useful when you have to acquire lock on primitive types e.g int or decimal etc.
In my opinion if you are use this technique as everybody else suggested then you don't need to perform null check twice. e.g. in accepted answer Cris has used if condition twice which really doesn't make any difference because the locked object is different then what is actually being modified, if you are locking on a different object then performing the first null check is useless and waste of cpu.
I would suggest the following piece of code;
object readonly syncRootEmployee = new object();
List<Employee> employeeList = null;
List<EmployeePhoto> employeePhotoList = null;
public void AddEmployee(Employee employee, List<EmployeePhoto> photos)
{
lock (syncRootEmployee)
{
if (employeeList == null)
{
employeeList = new List<Employee>();
}
if (employeePhotoList == null)
{
employeePhotoList = new List<EmployeePhoto>();
}
employeeList.Add(employee);
foreach(EmployeePhoto ep in photos)
{
employeePhotoList.Add(ep);
}
}
}
I can't see any race condition in here if anybody else see race condition please do respond in comments. As you can see in above code it resolve 3 problem at once, one no null check required before locking, second it creates a critical section without locking two shared sources, and third locking more than one object cause deadlocks due to lack of attention while writing code.
Following is how I use the locks on primitive types.
object readonly syncRootIteration = new object();
long iterationCount = 0;
long iterationTimeMs = 0;
public void IncrementIterationCount(long timeTook)
{
lock (syncRootIteration)
{
iterationCount++;
iterationTimeMs = timeTook;
}
}
public long GetIterationAvgTimeMs()
{
long result = 0;
//if read without lock the result might not be accurate
lock (syncRootIteration)
{
if (this.iterationCount > 0)
{
result = this.iterationTimeMs / this.iterationCount;
}
}
return result;
}
Happy threading :)