2012-01-27 37 views
1

下面是一個簡單的併發節流閥和相關測試。它並不像指定的那樣更經常地觀察到併發性,但我不確定爲什麼?併發調節器

[TestFixture] 
public class ConcurrencyThrottleTests 
{ 
    [Test] 
    public void ThrottleTest() 
    { 
     var throttle = new ConcurrencyThrottle(2); 
     var maxReg = new MaxRegister(); 
     var threadPool = new SmartThreadPool(); 

     var state = new DoWorkState {Throttle = throttle, MaxRegister = maxReg}; 
     var workItemResults = new List<IWaitableResult>(); 

     for (int i = 0; i < 1000; i++) 
      workItemResults.Add(threadPool.QueueWorkItem(DoWork, state)); 

     SmartThreadPool.WaitAll(workItemResults.ToArray()); 

     Assert.IsTrue(maxReg.MaxValue <= 2); 
    } 

    public void DoWork(object state) 
    { 
     var doWorkState = (DoWorkState)state; 

     doWorkState.Throttle.Enter(); 
     try 
     { 
      doWorkState.MaxRegister.Increment(); 

      Thread.Sleep(10); 

     } 
     finally 
     { 
      doWorkState.MaxRegister.Decrement(); 
      doWorkState.Throttle.Exit(); 
     } 
    } 

    public class DoWorkState 
    { 
     public IConcurrencyThrottle Throttle { get; set; } 
     public MaxRegister MaxRegister { get; set; } 
    } 

    public class ConcurrencyThrottle : IConcurrencyThrottle 
    { 
     private readonly int _max; 
     private readonly object _lock = new object(); 
     private readonly MaxRegister _register = new MaxRegister(); 

     public ConcurrencyThrottle(int max) 
     { 
      _max = max; 
     } 

     public void Exit() 
     { 
      lock (_lock) 
      { 
       _register.Decrement(); 

       Monitor.Pulse(_lock); 
      } 
     } 

     public void Enter() 
     { 
      lock (_lock) 
      { 
       while (_register.CurrentValue == _max) 
        Monitor.Wait(_lock); 

       _register.Increment(); 
      } 
     } 
    } 

    public class MaxRegister 
    { 
     public int MaxValue { get; private set; } 
     public int CurrentValue { get; private set; } 

     public void Increment() 
     { 
      MaxValue = Math.Max(++CurrentValue, MaxValue); 
     } 

     public void Decrement() 
     { 
      CurrentValue--; 
     } 
    } 
} 
+2

首先,你的共享MaxRegister類不是線程安全的......你確定這不是問題嗎? – 2012-01-27 10:25:31

+0

我只是從節氣門的鎖定部分調用它,所以我不認爲這是問題。 – 2012-01-27 10:28:25

+2

但是在節流閥中你的併發性= 2,所以DoWork的try塊同時有兩個線程,對吧? (我指的是從DoWork調用MaxRegister.Increment/Decrement,而不是從ConcurrencyThrottle調用。) – 2012-01-27 10:32:10

回答

1

的問題是,雖然併發限於兩個,你還在使用節流碼內的非線程安全的對象(MaxRegister):

doWorkState.Throttle.Enter(); 
try 
{ 
    doWorkState.MaxRegister.Increment(); 
    Thread.Sleep(10); 
} 
finally 
{ 
    doWorkState.MaxRegister.Decrement(); 
    doWorkState.Throttle.Exit(); 
} 

MaxRegister.IncrementMaxRegister.Decrement涉及沒有鎖定,也不要使用原子操作,這些操作將會使它們變得安全。

使用Interlocked.Decrement就足夠了MaxRegister.Decrement,Increment比較難,因爲你有兩個值。這可能是你可以使用Interlocked.IncrementCurrentValue,記住結果,然後如果需要,原子使用CompareExchange增加MaxValue。或者只是使用鎖來進行這兩種操作:)

請注意,爲了使用Interlocked,您需要遠離使用自動實現的屬性,因爲互鎖方法具有ref參數。

0

從我所看到的,對於初學者在 「ConcurrencyThrottle.Enter」 你有:

while (_register.CurrentValue == _max) 

這將打破,如果CurrentValue的是大於max,所以也許你應該有:

while (_register.CurrentValue >= _max) 

其次,你有

var maxReg = new MaxRegister(); 

在ThrottleTest方法,隨後再爲其分配和MANIP ulate在你的「狀態」變量中 - 但是,這個變量與ConcurrencyThrottle類中聲明的變量完全無關。因此,在「doWorkState」中遞增或遞減對「ConcurrencyThrottle.Enter」中測試的結果沒有任何影響。

我會被誘惑最大ConcurrencyThrottle獨立的,並把它像這樣:

public class ConcurrencyThrottle : IConcurrencyThrottle 
{ 
    private int Max { get; set;} 
    private static object _lock = new object(); 
    private static object _concurrencyLock = new object(); 
    public static MaxRegister Register { get; set; } 
    private static volatile _Default; 

    private ConcurrencyThrottle() 
    { 
     Register = new MaxRegister 
     { 
      CurrentValue = 0, 
      MaxValue = 2 
     }; 
    } 

    public static ConcurrencyThrottle Default 
    { 
     get 
     { 
      lock (_lock) 
      { 
       if(_Default == null) 
       { 
        _Default = new ConcurrencyThrottle(); 
       } 

       return_Default; 
      } 
     } 
    } 

    public void Enter() 
    { 
     lock (_concurrencyLock) 
     { 
      while (Register.CurrentValue == _max) 
       Monitor.Wait(_concurrencyLock); 

      Register.Increment(); 
     } 
    } 

    etc etc 

這顯然只是一個建議,但我可以理解MaxRegister在ConcurrencyThrottle是毫無關係的一個你正在操作「DoWork」。

希望有幫助,快樂的編碼!
乾杯,
Chris。

+0

由於ConcurrencyThrottle正確,_register.CurrentValue永遠不會超過_max,所以我在那裏安全。測試中的MaxRegister實例和ConcurrencyThrottle中的MaxRegister實例按設計分開。答案僅僅是MaxRegister不是線程安全的,這意味着測試是有缺陷的,因爲它可以在兩個線程上同時調用MaxReigster。見Skeets先生上面的答案。謝謝。 – 2012-01-27 11:03:07