Locking for internal operations






4.43/5 (4 votes)
Pausing external operations on an object when you want to lock the object for internal operations.
Introduction
There are times when you want to stop the external operations on an object to do internal operations, say for example you need to cleanup internal data structures to free up memory so you need to halt the use of the object in the meantime.
Although we are using lock
s to do this we are not blocking the external
operations of the object so the object will retain multi-thread support and the operations are concurrent.
Using the code
Below is the class using this technique. We have a class with three external methods: dash
, dot
, and freeup
,
when freeup
is called the other two methods will block until done
so you are sure that freeup
can do all that it needs
without worrying about concurrency issues.
All you need to do for your own classes is to add the CheckLock
method at the start
of your own methods, so there is minimal changes to the existing code.
class someclass
{
private bool _internalOperation; // flag for checking if we need to lock
private object _lock = new object();
public void dash()
{
CheckLock(); // blocks until internal operation is done
Console.Write("-");
}
public void dot()
{
CheckLock(); // just add this line to your own code
Console.Write(".");
}
public void freeup()
{
lock (_lock)
{
_internalOperation = true;
// some really important operation here
for (int i = 0; i < 100; i++)
{
Console.Write("F");
Thread.Sleep(20);
}
_internalOperation = false;
}
}
private void CheckLock()
{
if (_internalOperation) // don't lock unless needed
lock (_lock) ; // the good stuff is here
}
}
As you can see we are using locks in the freeup
method but not in the other methods so we retain concurrency of operations on the object i.e., dash
and dot
do not block each other.
To illustrate this below is the sample code that uses the class above:
class Program
{
public delegate void MethodCall();
public static bool end = false;
static void Main(string[] args)
{
someclass s = new someclass();
Task.Factory.StartNew(() =>
{
while (!end)
{
Thread.Sleep(2000);
s.freeup();
}
});
Task.Factory.StartNew( () => Exec(s.dot) );
Task.Factory.StartNew( () => Exec(s.dash) );
Task.Factory.StartNew(() =>
{
Console.ReadKey();
end = true;
});
while (!end)
Thread.Sleep(1000);
}
private static void Exec(MethodCall method)
{
while (!end)
{
method();
Thread.Sleep(5);
}
}
}
The above code uses .NET 4 Task
framework and starts four threads: one to call the dash
, one for the dot
,
one to exit the program if you press any key, and one to call the freeup
every
two seconds. So you will see a series of '-
'
and '.
' in the console output with a series of 'F
' characters which show that the object is multi-threaded and blocks
for the internal operation so you don't see anything break up the 'F
' characters.
v2 - mindful of race conditions
To get around race conditions and to make the use of the code easier I have changed the code to use a helper class and a queue as below:
class someclass
{
// add this class to your own code
class L : IDisposable
{
someclass _sc;
public L(someclass sc) // change to the class name
{
_sc = sc;
_sc.CheckLock();
}
void IDisposable.Dispose()
{
_sc.Done();
}
}
private volatile bool _internalOperation;
private Queue _que = new Queue(); // added a queue
private object _lock = new object();
public void dash()
{
using (new L(this)) // better and cleaner way to wrap things
{
// your normal code goes here
Console.Write("-");
}
}
public void dot()
{
using (new L(this))
{
Console.Write(".");
}
}
public void freeup()
{
lock (_lock)
{
_internalOperation = true;
while (_que.Count > 0) Thread.SpinWait(1); // wait till pending operations are done
for (int i = 0; i < 100; i++)
{
Console.Write("F");
Thread.Sleep(20);
}
_internalOperation = false;
}
}
private void CheckLock()
{
if (_internalOperation)
lock (_lock) ;
_que.Enqueue(1);
}
private void Done()
{
if (_que.Count > 0)
_que.Dequeue();
}
}
A couple of things to do in your own code and you are good to go:
- Add the
L
class to your own code. - Change the class type passed to the
L
class (i.e.someclass
to your own class). - Add a
Queue
to your main class. - Wrap your public methods with the
using(new L(this))
line.
freeup()
starts and all public operations stop in the meantime. Points of Interest
Personally I used to use Thread.Sleep
a lot for these types of checking which took a performance toll and looked really ugly,
and while thinking about it I came to this solution which seems much cleaner.
The if
statement in CheckLock
insures that you do not take a performance hit in locking when you don't need to.
A very important point is that the public methods on the main class are concurrent and do not lock each other out so you can call them concurrently.
History
- Initial release: 28th March 2013.
- Update : 31st March 2013
- Moved changing _internalOperation into the lock in freeup()
- Update : 1st April 2013
- New symantics using a queue and a helper class