2009-06-07 14 views
12

何と使い方?C#でFIFO順序を保証する同期クラスはありますか?

毎秒DBに挿入するタイマーがあり、タイマーハンドラとメインスレッドの間に共有リソースがあります。 私は、タイマーハンドラが挿入に1秒以上かかる場合、待機スレッドは順番に実行されるべきであることを保証したいと思います。 これは私のタイマハンドラ

private void InsertBasicVaraibles(object param) 
{ 
      try 
      { 
       DataTablesMutex.WaitOne();//mutex for my shared resources 
       //insert into DB 
      } 
      catch (Exception ex) 
      { 
       //Handle 
      } 
      finally 
      { 
       DataTablesMutex.ReleaseMutex(); 
      } 
} 

のサンプルコードである。しかし、現在、ミューテックスは、任意の順序を保証するものではありません。 詳細な質問をした後、答えがありません!!!

+0

は再入の問題については、私はこれにコードを変更しました。 1つのスレッドが入り、それが出るまで誰も入らない! –

+10

@Mitch:しかし、待たなければならなかった最初のスレッドが直後にブロック解除されるという保証はありません。そうではありません。ロックはFIFOではありません。 –

+2

(つまり、現在T1がロックを保持している場合、T2はそれを取得しようとします。その後T3はそれを取得しようとしますが、T3がT2を取得する前に取得する保証はありません)。 –

答えて

26

あなたがこれを行うには、独自のクラスを記述する必要があります、私は(サイトのドメインが経過したかのように見えるので、貼り付け)この例が見つかりました:使用方法の

using System.Threading; 

public sealed class QueuedLock 
{ 
    private object innerLock; 
    private volatile int ticketsCount = 0; 
    private volatile int ticketToRide = 1; 

    public QueuedLock() 
    { 
     innerLock = new Object(); 
    } 

    public void Enter() 
    { 
     int myTicket = Interlocked.Increment(ref ticketsCount); 
     Monitor.Enter(innerLock); 
     while (true) 
     { 

      if (myTicket == ticketToRide) 
      { 
       return; 
      } 
      else 
      { 
       Monitor.Wait(innerLock); 
      } 
     } 
    } 

    public void Exit() 
    { 
     Interlocked.Increment(ref ticketToRide); 
     Monitor.PulseAll(innerLock); 
     Monitor.Exit(innerLock); 
    } 
} 

例:

QueuedLock queuedLock = new QueuedLock(); 

try 
{ 
    queuedLock.Enter(); 
    // here code which needs to be synchronized 
    // in correct order 
} 
finally 
{ 
    queuedLock.Exit(); 
} 

Source via Google cache

+4

QueuedLockがIDisposableだった場合、これはもっときれいになります –

+0

今度はrefによってvolatileを渡すことはC#compilerでは推奨されません。揮発性物質として扱われる。それ以外の今日の問題に対する完璧な解決策! – AlexanderVX

+0

完璧に作業しました! – merger

1

任意の組み込みの同期オブジェクトには何の保証順序はありません:http://msdn.microsoft.com/en-us/library/ms684266(VS.85).aspx

あなたが保証オーダーをしたい場合は、それが聞こえるかもしれないほど簡単ではないことけれども注意し、自分を試してみて、何かを構築する必要があります特に、複数のスレッドが同じ時刻に(近くに)同期点に到達したときに発生します。ある程度、リリースされる順番はいつも「無作為」になるでしょう。というのも、どの順番でポイントに達するかを予測できないからです。本当に重要ですか?

+0

リンクは当然OSプリミティブに関するものです... .NETのものはこれらの上に構築されるので、FIFOが保証されないことを意味すると思います.Jonの答えも参照してください。 – jerryjvl

+1

問題は一般的に、スレッドが同時にロックを取得しようとするときにランダムな順序でロックに入ることではありません。この問題は、長い時間ロックを入力しようとしているスレッドが、ずっと後でロックを入力しようとしていたスレッドによって行を切られた場合に発生します。 1000の要求が常に共有リソースにアクセスしようとしているビジーなWebサーバーを想像してください。そして、後で要求がロックを盗み続けているため、ロックで待機する最初のスレッドは待機し続けます。 –

10

Joe Duffyの "Concurrent Programming on Windows"というと、のように聞こえます。通常、は、.NETモニタからFIFOの動作を取得しますが、発生しない状況があります。

Page 273「モニタはカーネルオブジェクトを内部的に使用するため、前の章で説明したように、OSの同期メカニズムにも同様の大まかなFIFO動作が発生します」モニタは不公平なので、別のスレッド待機中のスレッドがロックを取得しようとする前にロックを取得し、ロックを取得すると、不正なスレッドはロックを取得することが許可されます。

私はすぐに「前の章の」参照セクションを見つけることができませんが、それはロックは、スケーラビリティを向上させ、ロック船団を削減するには、Windowsの最新版では、意図的に不公平行われているノートを行います。

ロックがFIFOである必要がありますか?おそらく、この問題にアプローチする別の方法があります。私はFIFOであることが保証されている.NETのロックを知らない。

+0

Jon Skeetはいつもの通り - Win2k3 SP2(ish?)以上には100%フェアロックがありません。これは意図的です。 –

+4

-1:実際のソリューションは提供されていません – Svisstack

+0

本当に楽しいのは、「卑劣なスレッド」がまさにモニターをリリースしたときです。そして、こうして、それはループで永遠に続き、もう一方のスレッドを飢えさせます。ちょうどCE 5の.Net Compact Frameworkのいくつかの古いコードでこの動作に遭遇しました。 – reirab

7

スレッドの実行順序に依存しないようにシステムを再設計する必要があります。例えば、あなたのスレッドが1秒以上かかるかもしれないDB呼び出しをスレッドに持たせるのではなく、スレッドが実行するコマンドをキューのようなデータ構造に置くようにします(または、「これは別のものの前にあること ")。その後、空き時間に、キューを排水し、適切な順序でDBを1つずつ挿入します。

1

は、実際の答えは良いですが、

private void InsertBasicVaraibles() 
    { 
     int functionStopwatch = 0; 
     while(true) 
     { 

      try 
      { 
      functionStopwatch = Environment.TickCount; 
      DataTablesMutex.WaitOne();//mutex for my shared resources 
      //insert into DB 
      } 
      catch (Exception ex) 
      { 
      //Handle    
      } 
      finally    
      {     
       DataTablesMutex.ReleaseMutex(); 
      } 

      //simulate the timer tick value 
      functionStopwatch = Environment.TickCount - functionStopwatch; 
      int diff = INSERTION_PERIOD - functionStopwatch; 
      int sleep = diff >= 0 ? diff:0; 
      Thread.Sleep(sleep); 
     } 
    } 
+0

素敵な睡眠のロジック!今すぐすべてのコードを書き直す:) – divinci

0

はマシューブリンドニーの答えにフォローアップ、以下のように私は、バックグラウンドスレッドにタイマーを削除することで問題を解決し、(以前のタイマハンドラ)メソッドを実行します。

lock (LocalConnection.locker) {...} 

からコードを変換すると、あなたはどちらかIDisposableインターを行うか、何ができる

場合は私がやったこと:それはすべての呼び出しに新しい不可視のオブジェクトを作成しますので、私はIDisposableを反対決め

public static void Locking(Action action) { 
    Lock(); 
    try { 
    action(); 
    } finally { 
    Unlock(); 
    } 
} 

LocalConnection.Locking(() => {...}); 

。ロックがFIFOである定義によって

public sealed class QueuedLock { 
    private object innerLock = new object(); 
    private volatile int ticketsCount = 0; 
    private volatile int ticketToRide = 1; 
    ThreadLocal<int> reenter = new ThreadLocal<int>(); 

    public void Enter() { 
     reenter.Value++; 
     if (reenter.Value > 1) 
      return; 
     int myTicket = Interlocked.Increment(ref ticketsCount); 
     Monitor.Enter(innerLock); 
     while (true) { 
      if (myTicket == ticketToRide) { 
       return; 
      } else { 
       Monitor.Wait(innerLock); 
      } 
     } 
    } 

    public void Exit() { 
     if (reenter.Value > 0) 
      reenter.Value--; 
     if (reenter.Value > 0) 
      return; 
     Interlocked.Increment(ref ticketToRide); 
     Monitor.PulseAll(innerLock); 
     Monitor.Exit(innerLock); 
    } 
} 
関連する問題