FIFO Thread lock
Did you know that when you put lock on a code section the thread access order is not FIFO? I mean the order that the threads will gain access to that locked code is not necessarily in the order they requested the lock?
I obviously found this the hard way. It all started when one of the members in my team needed to write a web service that calls a method on a shared resource. This resource can only support one call at a time and so needed thread synchronization. The solution was obvious, he surrounded the code that calls the method with a lock statement. We know that the call to the shared resource takes 2 seconds and it will have no more than 10 threads calling it at the same time which means the longest a web service call will take is about 20 seconds. When we run a test simulation we found we get timeouts after 30 seconds and even longer.
So I did some investigation (involving writing to a log file from everywhere in the code) and found that also most of the calls to the shared method are performed more or less in the order they come into the IIS a few are getting out of the expected queue and are performed only after more than a minute. I searched about this on the Internet and found that indeed the order by which thread synchronizations are released is not guaranteed to be the order they were requested. You can read about it here and here.
So I wrote a lock class of my own that release threads according to the requests order. It uses a queue of ManualRestEvents. one for each thread and when a thread release a lock it signals the next thread in the queue to be released from lock. This is obviously if there is a waiting thread.
Here is the code:
using System.Collections.Generic;
using System.Threading;
namespace ShaharRon.Samples
{
public class FifoLock
{
//Queue for the threads sync objects
static private Queue<ManualResetEvent> s_locksQueue = new Queue<ManualResetEvent>();
//Count of threads including the non-locked one
static private int s_inProcessCount = 0;
//Sync object
private ManualResetEvent _manualResetEvent = new ManualResetEvent(true);
/// <summary>
/// Call this function before starting the work to be synchronized. It will
/// wait to be released. The release is in the order the threads called on it.
/// </summary>
public void Lock()
{
lock (s_locksQueue)
{
s_inProcessCount++;
//If other threads are running create a lock for this thread and
//put it in the queue else just continue without locking
if (s_inProcessCount > 1)
{
_manualResetEvent.Reset();
s_locksQueue.Enqueue(_manualResetEvent);
}
}
//Wait to be released. It will not wait if there was no other
//threads and no lock was created
_manualResetEvent.WaitOne();
}
/// <summary>
/// Call this function after the work to be synchronized is done to
/// release the next waiting thread.
/// </summary>
public void Unlock()
{
lock (s_locksQueue)
{
s_inProcessCount--;
//Release the next thread in the queue if exists and remove it
//from the queue.
if (s_locksQueue.Count > 0)
{
s_locksQueue.Dequeue().Set();
}
}
}
}
}
Here is how to use it:
FifoLock myLock = new FifoLock();
try
{
myLock.Lock();
//do what you need to do
//...
}
finally
{
myLock.Unlock();
}