私はC#でスレッドセーフプロパティを作成しようとしていますが、正しいパスにいることを確認したいです-これが私がしたことです-
private readonly object AvgBuyPriceLocker = new object();
private double _AvgBuyPrice;
private double AvgBuyPrice
{
get
{
lock (AvgBuyPriceLocker)
{
return _AvgBuyPrice;
}
}
set
{
lock (AvgBuyPriceLocker)
{
_AvgBuyPrice = value;
}
}
}
この投稿を読むと、これが正しい方法ではないように思えます-
ただし、この記事はそうでないことを示唆しているようです。
http://www.codeproject.com/KB/cs/Synchronized.aspx
誰かがより決定的な答えを持っていますか?
編集:
このプロパティのGetter/Setterを実行する理由はb/cです。実際には、設定されたときにイベントを発生させたいので、コードは実際には次のようになります-
public class PLTracker
{
public PLEvents Events;
private readonly object AvgBuyPriceLocker = new object();
private double _AvgBuyPrice;
private double AvgBuyPrice
{
get
{
lock (AvgBuyPriceLocker)
{
return _AvgBuyPrice;
}
}
set
{
lock (AvgBuyPriceLocker)
{
Events.AvgBuyPriceUpdate(value);
_AvgBuyPrice = value;
}
}
}
}
public class PLEvents
{
public delegate void PLUpdateHandler(double Update);
public event PLUpdateHandler AvgBuyPriceUpdateListener;
public void AvgBuyPriceUpdate(double AvgBuyPrice)
{
lock (this)
{
try
{
if (AvgBuyPriceUpdateListener!= null)
{
AvgBuyPriceUpdateListener(AvgBuyPrice);
}
else
{
throw new Exception("AvgBuyPriceUpdateListener is null");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
私はコードスレッドを安全にすることにかなり慣れていないので、まったく間違った方法でそれを行っているかどうかお気軽に教えてください!
意志
プリミティブ値があるため、このロックは正常に機能します-他の質問の問題は、プロパティ値がより複雑なクラス(可変参照型)であったことです-ロックは、保持するdouble値のインスタンスへのアクセスと取得を保護しますあなたのクラス。
一方、プロパティ値が可変参照型である場合、ロックはメソッドを使用して取得されたクラスインスタンスの変更を保護しません。
あなたが書いたように、ロックは無意味です。たとえば、変数を読み取るスレッドは次のことを行います。
手順3の後、別のスレッドが値を変更するのを止めるものはありません。NETの変数アクセスはアトミックであるため(以下の注意を参照)、ロックは実際にはあまり達成されません。ロックされていない例とは対照的です:
別のスレッドがステップ1と2の間で値を変更する場合がありますが、これはロックされた例と同じです。
何らかの処理を行っているときに状態が変わらないようにしたい場合は、ロックのコンテキスト内で値を読み取る必要がありますおよびその値を使用して処理を行う:
そうは言っても、変数にアクセスするときにロックする必要がある場合があります。これらは通常、基盤となるプロセッサの理由によるものです。たとえば、32ビットマシンではdouble
変数を単一の命令として読み書きできないため、ロックする(または代替戦略を使用する)必要があります。破損した値は読み取られません。
スレッドセーフティは、変数に追加すべきものではなく、「ロジック」に追加すべきものです。すべての変数にロックを追加した場合、コードは必ずしもスレッドセーフであるとは限りませんが、非常に遅くなります。スレッドセーフプログラムを作成するには、コードを見て、複数のスレッドが同じデータ/オブジェクトを使用している可能性がある場所を判断します。これらすべての重要な場所にロックまたはその他の安全対策を追加します。
たとえば、次の疑似コードを想定します。
void updateAvgBuyPrice()
{
float oldPrice = AvgBuyPrice;
float newPrice = oldPrice + <Some other logic here>
//Some more new price calculation here
AvgBuyPrice = newPrice;
}
このコードが複数のスレッドから同時に呼び出された場合、ロックロジックは役に立ちません。スレッドAがAvgBuyPriceを取得し、いくつかの計算を行うことを想像してください。これが完了する前に、スレッドBもAvgBuyPriceを取得して計算を開始しています。その間にスレッドAが完了し、新しい値をAvgBuyPriceに割り当てます。ただし、しばらくすると、スレッドB(まだ古い値を使用している)によって上書きされ、スレッドAの作業は完全に失われます。
では、これをどのように修正しますか?ロックを使用する場合(これは最もgliくて遅いソリューションですが、マルチスレッドを開始する場合は最も簡単です)、AvgBuyPriceを変更するすべてのロジックをロックに入れる必要があります。
void updateAvgBuyPrice()
{
lock(AvgBuyPriceLocker)
{
float oldPrice = AvgBuyPrice;
float newPrice = oldPrice + <Some other code here>
//Some more new price calculation here
AvgBuyPrice = newPrice;
}
}
ここで、スレッドAがまだビジーである間にスレッドBが計算を行いたい場合、スレッドAが完了するまで待機し、新しい値を使用して作業を行います。ただし、AvgBuyPriceを変更する他のコードも、動作中にAvgBuyPriceLockerをロックする必要があることに注意してください。
それでも、頻繁に使用すると、これは遅くなります。ロックは高価であり、ロックを回避するための他の多くのメカニズムがあります。ロックのないアルゴリズムを検索するだけです。
とにかくdoubleの読み取りと書き込みはアトミックです( source ) doubleの読み取りと書き込みはアトミックではないため、ロックを使用してdoubleへのアクセスを保護する必要がありますが、多くのタイプでは読み取りと書き込みはアトミックであるため、以下も同様に安全です。
private float AvgBuyPrice
{
get;
set;
}
私のポイントは、単に各プロパティを保護するよりも、スレッドセーフが複雑だということです。簡単な例を挙げると、2つのプロパティAvgBuyPrice
とStringAvgBuyPrice
があるとします。
private string StringAvgBuyPrice { get; set; }
private float AvgBuyPrice { get; set; }
そして、こうして平均購入価格を更新するとします:
this.AvgBuyPrice = value;
this.StringAvgBuyPrice = value.ToString();
これは明らかにスレッドセーフではなく、上記の方法でプロパティを個別に保護してもまったく役に立ちません。この場合、プロパティごとのレベルではなく、異なるレベルでロックを実行する必要があります。