1

Dictionary インスタンスが 2 つのスレッド間で共有される .NET 3.5 アプリケーションがあります。ディクショナリ自体はスレッドセーフではないことは理解していますが、ディクショナリを変更できるスレッドは 1 つしかなく、もう 1 つのスレッドは作業を実行するときに最新の値を保持する必要があるだけです。(厳密な意味では、最新であることは難しい要件ではありません)

1 つのスレッドが断続的なシリアル データを受信し、Set 関数を呼び出してディクショナリ内の値を変更しています。(このディクショナリは初期設定後は固定サイズで、基本的にはスパース配列として使用しています)

2 番目のスレッドは、現在ディクショナリに格納されている値を収集し、GetLatestValues() を介して何らかの処理を行います。

public class HwMemoryMap{
   Dictionary<int, HwDataItem> HwCache;

   public void Set(HwDataItem dataItem){
       HwCache[dataItem.PtId] = dataItem;
       MemoryBarrier();
   }

   public List<HwDataItem> GetLatestValues(){
       System.Threading.Thread.MemoryBarrier();
       List<HwDataItem> HwDataItemList = new List<HwDataItem>();
       // do work here to pull appropriate values out of HwCache
       HwDataItemList.Add(HwCache[0]); // etc
       return HwDataItemList;
   }
}

ここでの MemoryBarrier() 呼び出しは、特定の Key のディクショナリ Value への変更がすべてのスレッド/コアに伝達されることを保証するのに十分ですか?

私のテストでは問題は明らかになりませんでしたが、これらの問題の性質を考えると、それは私に何の慰めにもなりません.

4

2 に答える 2

2

いいえ、これは安全ではありません。データ構造への書き込みと読み取りを同時に行うリスクは依然としてあります。書き込みの頻度が十分に低い場合にうまく機能する、使用できるトリックがあります。基本的に、によって参照されるデータ構造HwCacheが不変のままであることを確認します。データ構造を変更するたびに、最初にそれを新しいインスタンスにコピーし、排他ロック内の新しいインスタンスを変更します。HwCache次に、変更が完了したら、参照を新しいインスタンスと交換します。これが正しく機能するには、HwCacheとしてマークする必要がありますvolatile

public class HwMemoryMap
{
  private object lockobj = new object();
  volatile Dictionary<int, HwDataItem> HwCache;

  public void Set(HwDataItem dataItem)
  {
    lock (lockobj)
    {
      var copy = new Dictionary<int, HWDataItem>(HwCache);
      copy[dataItem.PtId] = dataItem;
      HwCache = copy;
    }
  }

  public List<HwDataItem> GetLatestValues()
  {
    var local = HwCache;
    var HwDataItemList = new List<HwDataItem>();
    // do work here to pull appropriate values out of local
    HwDataItemList.Add(local[0]); // etc
    return HwDataItemList;
  }
}
于 2012-04-11T13:55:16.980 に答える
1

私はC#にあまり精通していませんが、「内部」で何が起こっているのかについては推測しません。特に、挿入コードがデータ構造にアクセスするのはそれだけであると想定している場合、スペースの割り当て中などに、部分的に更新された参照が残る可能性があります。

次の(簡略化された)コードについて考えてみます。

Insert(TKey key, TVal val) {
    if (this.size > this._threshold) {
        // Allocate more space and
        // move to a new table
    }
    // Find location and insert
}

そのすべてが1つのストア内で発生している場合(C#が辞書を実装する方法がわかりません)、中間状態のいずれかが別の状態に伝播しても、メモリバリアはあなたを救いませんスレッド。

もっと理にかなっているのは、リーダー/ライターロックのペアです。一般的なケースが読み取りの場合、複数のスレッドですべてリーダーロックを取得できます。更新が必要な場合にのみ、書き込みスレッドへの排他的アクセスを許可する必要があります。

于 2012-04-11T01:40:52.093 に答える