2012-01-27 24 views
1

以下は、単純な同時実行スロットルと関連テストです。なぜなら、指定されているよりも並行性を監視するよりも頻繁には動作しませんが、なぜそれほどわかりませんか?同時実行スロットル

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

MaxRegister.IncrementMaxRegister.Decrementが伴う:

[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クラスはスレッドセーフではありません...それは問題ではないと確信していますか? –

+0

私はスロットルのロックセクションから呼び出すだけなので、それは問題ではないと思います。 –

+2

しかし、あなたはスロットルで並行性= 2を持っているので、DoWorkのtryブロックに2つのスレッドが同時に存在する可能性があります。 (私は、ConcurrencyThrottleからではなく、DoWorkからのMaxRegister.Increment/Decrementへの呼び出しを参照していました) –

答えて

1

問題は、並行性が2つに限定されているが、あなたはまだ絞られたコード内の非スレッドセーフなオブジェクト(MaxRegister)を使用しているということですロックしないようにしてください。また、安全にするために必要な操作であるInterlockedの原子を使用しないでください。

Interlocked.Decrementを使用すると、MaxRegister.Decrementで十分です。Incrementは2つの値を持つので、より難しくなります。可能であれば、CurrentValueInterlocked.Incrementを使用し、結果を覚えておき、必要に応じて灰色でCompareExchangeを使用してMaxValueを増やしてください。または、両方の操作でロックを使用してください:

Interlockedを使用するには、インターロックされたメソッドにrefパラメータがあるため、自動的に実装されたプロパティを使用する必要はありません。私は "ConcurrencyThrottle.Enter" で先発ために、見ることができるものから、

0

あなたが持っている:はCurrentValueがmaxよりも大きかった場合、壊れる

while (_register.CurrentValue == _max) 

はので、多分あなたが持っている必要があります。

while (_register.CurrentValue >= _max) 

第2に、ThrottleTestメソッドで

var maxReg = new MaxRegister(); 

が割り当てられていますしかし、この変数は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 

これは明らかに単なる提案ですが、私はあなたのConcurrencyThrottleでMaxRegisterを理解することができるものから、1つあなたとは関係ありません"DoWork"で操作しています。

希望は、うれしいコーディングです!
乾杯、
Chris。

+0

ConcurrencyThrottleが正しいので、_register.CurrentValueは_maxを決して超えないので、私はそこで安全です。テストのMaxRegisterインスタンスとConcurrencyThrottleのMaxRegisterインスタンスは、それぞれ別個に設計されています。答えは、単にMaxRegisterがスレッドセーフではないということです。つまり、MaxReigsterを2つのスレッドで同時に呼び出すことができるため、テストに欠陥があることを意味していました。上記のMr Skeetsの答えを見てください。ありがとう。 –