2015-03-03 12 views
5

Eksperymentuję z wątkami w języku C#, a w rezultacie utworzyłem następującą klasę. Starałem się unikać sytuacji związanych z wyścigami, a mimo to pojawił się impas.Zakleszczenie w klasie puli obiektów

Klasa wykorzystuje dwie różne blokady, jedną blokadę do prostej operacji, a dodatkowo blokadę Monitor oczekiwania na wypadek, gdyby obiekt nie był gotowy. Pierwotnie używałem EventWaitHandle, ale odkryłem, że warunki wyścigu były nieuniknione z powodu pierwszeństwa.

Należy pamiętać, że Monitor.Pulse nie może poprzedzać Monitor.Wait, więc co jeszcze może spowodować zakleszczenie? W przypadku, gdy 5 wątków używa klasy TestPool o pojemności 4, impas zawsze występuje w SpinLock w nieregularnym momencie.

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; 
    } 
} 
+0

Spinlocks vs MUTEX http://stackoverflow.com/questions/5869825/when-should-one-use-a-spinlock-instead-of-mutex Nie mówiąc to rozwiąże problem, ale coś zbadać. – JNYRanger

+0

Czy używasz SetCapacity poza ctor? – usr

+0

@usr - Jest przeznaczony, ale ja wypiszę jego prywatną wersję, aby pominąć blokowanie. Należy pamiętać, że wszelkie zabezpieczenia są celowo nieobecne. – Tcqqp

Odpowiedz

0

Naprawiono. Musiałem umieścić następujący kod poza blokadą Monitor.

Lock(); 

return units[--waitUnitPos];