I'm experimenting with threading in C#, and I've created the following class as a result. I've tried to avoid any cases of race conditions, yet a deadlock occurs on use.
The class uses two different locks, one spinlock for straightforward operations, and additionally a Monitor
lock to wait in case no object is ready. I originally used EventWaitHandle
, but discovered that race conditions were inevitable due to WaitOne
/Set
precedence.
Note that Monitor.Pulse
could not precede Monitor.Wait
, so what else could cause a deadlock? In the case where 5 threads use a TestPool
class with a capacity of 4, the deadlock always occurs at SpinLock
at an irregular moment.
internal class TestPool<T> where T : class
{
private int capacity;
private int unitPos;
private int waitUnitPos;
private int waitCount;
private int lockState;
private object lockObj;
private T[] units;
private Func<T> unitFactory;
public TestPool(int capacity, Func<T> unitFactory)
{
this.lockObj = new object();
this.unitFactory = unitFactory;
Init(capacity);
}
public T Fetch()
{
T unit;
Lock();
unit = (unitPos != capacity) ? units[unitPos++] : Wait();
Unlock();
return unit;
}
public void Store(T unit)
{
Lock();
if (waitCount == 0)
{
units[--unitPos] = unit;
}
else
{
Pulse(unit);
}
Unlock();
}
private T Wait()
{
waitCount++;
lock (lockObj)
{
Unlock();
Monitor.Wait(lockObj);
Lock();
return units[--waitUnitPos];
}
}
private void Pulse(T unit)
{
waitCount--;
units[waitUnitPos++] = unit;
lock (lockObj)
{
Monitor.Pulse(lockObj);
}
}
private void Lock()
{
if (Interlocked.CompareExchange(ref lockState, 1, 0) != 0)
{
SpinLock();
}
}
private void SpinLock()
{
SpinWait spinWait = new SpinWait();
do
{
spinWait.SpinOnce();
}
while (Interlocked.CompareExchange(ref lockState, 1, 0) != 0);
}
private void Unlock()
{
Interlocked.Exchange(ref lockState, 0);
}
private void Init(int capacity)
{
T[] tx = new T[capacity];
for (int i = 0; i < capacity; i++)
{
tx[i] = unitFactory.Invoke();
}
units = tx;
this.capacity = capacity;
}
}