3
Option Strict On

Public Class UtilityClass
  Private Shared _MyVar As String

  Public Shared ReadOnly Property MyVar() As String
    Get
      If String.IsNullOrEmpty(_MyVar) Then
        _MyVar = System.Guid.NewGuid.ToString()
      End If
      Return _MyVar
    End Get
  End Property

  Public Shared Sub SaveValue(ByVal newValue As String)
    _MyVar = newValue
  End Sub

End Class
4

2 に答える 2

5

ロックはスレッドセーフを追加するための優れた一般的なアプローチですが、null以外の値が書き込まれるとすぐにフィールドが不変になる、1回限りの準不変性を含む多くのシナリオでは、Threading.Interlocked.CompareExchangeより良い場合があります。基本的に、そのメソッドはフィールドを読み取り、フィールドが指定された「比較」値と一致する場合にのみ、他の人がフィールドに触れる前に新しい値を書き込みます。いずれの場合も読み取られた値を返します。2つのスレッドが同時に試行しCompareExchange、両方のスレッドがフィールドの現在の値を「比較」値として指定した場合、一方の操作は値を更新し、もう一方は更新せず、各操作は成功したかどうかを「認識」します。

CompareExchangeには2つの主な使用パターンがあります。1つ目は、変更可能なシングルトンオブジェクトを生成する場合に最も役立ちます。この場合、全員が同じインスタンスを表示することが重要です。

If _thing is Nothing then
    Dim NewThing as New Thingie() ' Or construct it somehow
    Threading.Interlocked.CompareExchange(_thing, NewThing, Nothing)
End If

このパターンはおそらくあなたが求めているものです。別のスレッドが入力してから実行するまでの間にスレッドが上記のコードを入力するとCompareExchange、両方のスレッドが新しいを作成する可能性があることに注意してくださいThingie。その場合、CompareExchangeに最初に到達したスレッドは、新しいインスタンスを_thingに格納し、他のスレッドはそのインスタンスを破棄します。このシナリオでは、スレッドは勝つか負けるかを気にしません。_thingには新しいインスタンスが含まれ、すべてのスレッドに同じインスタンスが表示されます。また、最初の読み取りの前にメモリバリアがないため、過去に_thingの値を調べたスレッドが、次のように見続ける可能性があることにも注意してください。Nothing何かが原因でキャッシュが更新されるまでですが、それが発生した場合の唯一の結果は、役に立たない新しいインスタンスが作成され、すでに書き込まれている検索結果がThingie破棄されるまでです。Interlocked.CompareExchange_thing

他の主な使用パターンは、不変オブジェクトへの参照を更新する場合、または(わずかな調整を加えて)IntegerやLongなどの特定の値型を更新する場合に役立ちます。

Dim NewThing, WasThing As Thingie
Do
    WasThing = _thing
    NewThing = WasThing.WithSomeChange();
Loop While Threading.Interlocked.CompareExchange(_thing, NewThing, WasThing) IsNot WasThing

このシナリオでは、Thingieを参照して、希望する方法で異なる新しいインスタンスを安価に作成できる手段があると仮定すると、スレッドセーフな方法で_thingに対してそのような操作を実行できます。たとえば、が与えられると、いくつかの文字が追加されStringた新しいものを簡単に作成できます。Stringスレッドセーフな方法で文字列にテキストを追加したい場合(一方のスレッドが追加Fredを試み、もう一方のスレッドが追加を試みた場合、最終的な結果は、のようなものではなく、Joe追加FredJoeまたはのいずれかになります)、上記コードは各スレッドを読み取り、テキストが追加されたバージョンを生成し、更新を試みます。他のスレッドが更新された場合JoeFredFrJoeed_thing_thing_thingその間に、最後に作成された文字列を破棄し、更新されたに基づいて新しい文字列を作成して、_thing再試行してください。

このアプローチは必ずしもロックアプローチよりも高速ではありませんが、利点があります。ロックを取得するスレッドが無限ループに陥ったり、ウェイレイドされたりすると、すべてのスレッドがロックされたリソースへのアクセスを永久にブロックされます。対照的に、WithSomeChanges()上記の方法が無限ループに陥った場合、の他のユーザーは_thing影響を受けません。

于 2013-01-11T18:51:47.190 に答える
4

マルチスレッドコードの場合、関連する質問は次のとおりです。複数のスレッドから状態を変更できますか?その場合、コードはスレッドセーフではありません。

あなたのコードでは、それが当てはまります。変異する場所がいくつかある_MyVarため、コードはスレッドセーフではありません。コードをスレッドセーフにする最良の方法は、ほとんどの場合、コードを不変にすることです。不変の状態は、デフォルトでは単にスレッドセーフです。さらに、スレッド間で状態を変更しないコードは、マルチスレッドコードを変更するよりも単純で、通常は効率的です。

残念ながら、コードを複数のスレッドから不変にすることができるかどうか(またはどのように)をコンテキストなしで確認することは不可能です。したがって、低速でエラーが発生しやすく(誤解しやすいことについては他の回答を参照)、誤った安心感を与えるロックに頼る必要があります。

以下は、ロックを使用してコードを正しくするための私の試みです。それは機能するはずです(しかし、誤った安心感を覚えておいてください):

Public Class UtilityClass
  Private Shared _MyVar As String
  Private Shared ReadOnly _LockObj As New Object()

  Public Shared ReadOnly Property MyVar() As String
    Get
      SyncLock _LockObj
        If String.IsNullOrEmpty(_MyVar) Then
          _MyVar = System.Guid.NewGuid.ToString()
        End If
        Return _MyVar
      End SyncLock
    End Get
  End Property

  Public Shared Sub SaveValue(ByVal newValue As String)
    SyncLock _lockObj
      _MyVar = newValue
    End SyncLock
  End Sub

End Class

いくつかのコメント:

  • の参照_MyVar変更_MyVarしてロックを失うため、ロックできません。別の専用のロックオブジェクトが必要です。
  • 変数への各アクセス、または少なくともすべての変更アクセスをロックする必要があります。それ以外の場合は、別の場所で変数を変更することで元に戻すことができるため、すべてのロックは無効になります。
  • 理論的には、値を読み取るだけの場合はロックする必要はありませんが、ロックを再確認する必要があり、エラーが増える可能性があるため、ここでは行いません。
  • 必ずしも読み取りアクセスをロックする必要はありませんが(前の2つのポイントを参照)、このプロパティへの読み取り/書き込みアクセスの並べ替えを防ぐために、どこかにメモリバリアを導入する必要がある場合があります。ルールが非常に複雑であるため、これがいつ関連するかはわかりません。これが、ロックが嫌いなもう1つの理由です。

全体として、コード設計を変更して、一度に1つのスレッドのみが特定の変数に書き込みアクセスできるようにし、スレッド間の必要なすべての通信を、同期されたデータ構造を介して明確に定義された通信チャネルに制限する方がはるかに簡単です。

于 2013-01-11T15:37:54.697 に答える