2

構造体の配列が与えられた場合:

public struct Instrument
{
    public double NoS;
    public double Last;
}

var a1 = new Instrument[100];

そして、単一の要素が最大で2つのスレッドによって同時に書き込まれる可能性があることに基づいてそれらの要素に書き込みを行うスレッドタスクプール(各ダブルフィールドに1つずつ)(トピックごとに効果的にアップストリームキューイングがあります)。

そして、doubleは64ビットでアトミックに記述できるという知識。(これを編集して、元々32ビットと誤って言った)

配列内のすべての値を使用して定期的に計算を実行する必要があり、計算中にそれらの値が一貫していることを望みます。

したがって、次の方法でアレイのスナップショットを作成できます。

var snapshot = a1.Clone();

今私が持っている質問は、同期の詳細に関するものです。メンバーを揮発性にした場合、読み取り/書き込みの取得/リリースはアレイレベルではないため、クローンの作成にはまったく役立たないと思います。

これで配列ロックを設定できましたが、これにより、配列にデータを書き込む最も頻繁なプロセスで多くの競合が発生します。だから理想的ではありません。

または、行ごとのロックを設定することもできますが、クローンを作成する前にすべてを取得する必要があるため、これは非常に面倒です。その間、書き込みはすべてバックアップされます。

スナップショットの値がマイクロ秒などの場合、最新の値でなくても問題はないので、ロックがなくても問題は解決できると思います。私の唯一の懸念は、持続的な期間のキャッシュの書き戻しがないシナリオが存在する可能性があるかどうかです。これは私が心配すべきことですか?ライターはTPLデータフローにあり、唯一のロジックは構造体に2つのフィールドを設定することです。ただし、関数スコープがキャッシュのライトバックと相関する傾向があるかどうかはわかりません。

考え/アドバイス?

編集:構造体の変数へのインターロック書き込みを使用した場合はどうなりますか?

edit2:書き込みの量は読み取りよりもはるかに多いです。Nos&Lastフィールドに書き込む2つの別個の同時サービスもあります。したがって、それらは同時に書き込まれる可能性があります。これにより、アトミック性の参照オブジェクトアプローチで問題が発生します。

edit3:詳細。配列が30〜1000の要素であり、各要素が1秒間に複数回更新される可能性があると想定します。

4

5 に答える 5

5

2つのdouble(2つの64ビット値)が含まれているためInstrument、アトミックに書き込むことはできません(64ビットマシンでも)。これは、Cloneメソッドが何らかの同期を行わずにスレッドセーフなコピーを作成できないことを意味します。

TLDR; 構造体を使用せず、不変のクラスを使用してください。

小さな再設計でおそらくもっと運がいいでしょう。不変のデータ構造と.NETFrameworkからの同時コレクションを使用してみてください。たとえばInstrument不変のクラスを作成します。

// Important: Note that Instrument is now a CLASS!! 
public class Instrument
{
    public Instrument(double nos, double last)
    {
        this.NoS = nos;
        this.Last = last;
    }

    // NOTE: Private setters. Class can't be changed
    // after initialization.
    public double NoS { get; private set; }
    public double Last { get; private set; }
}

このように更新するInstrumentということは、新しいものを作成する必要があることを意味します。これにより、これについて推論するのがはるかに簡単になります。Instrumentワーカーがこれを安全に実行できるようになったため、1つのスレッドだけが1つのスレッドで動作していることが確実な場合は、次のようになります。

Instrument old = a[5];

var newValue = new Instrument(old.NoS + 1, old.Last - 10);

a[5] = newValue;

参照型は32ビット(または64ビットマシンでは64ビット)であるため、参照の更新はアトミックであることが保証されています。これで、クローンは常に正しいコピーになります(遅れがない可能性がありますが、それは問題ではないようです)。

アップデート

質問を読み直した後、1つのスレッドがに書き込んでおらずInstrument、計測値に書き込んでいるため、誤読していることがわかりますが、解決策は実質的に同じです。不変の参照型を使用します。たとえば、簡単なトリックの1つは、プロパティNoSLastプロパティのバッキングフィールドをオブジェクトに変更することです。これにより、それらの更新がアトミックになります。

// Instrument can be a struct again.
public struct Instrument
{
    private object nos;
    private object last;

    public double NoS
    {
        get { return (double)(this.nos ?? 0d); }
        set { this.nos = value; }
    }

    public double Last
    {
        get { return (double)(this.last ?? 0d); }
        set { this.last = value; }
    }
}

プロパティの1つを変更すると、値はボックス化され、ボックス化された値は不変の参照型になります。このようにして、これらのプロパティを安全に更新できます。

于 2012-06-12T09:21:26.357 に答える
2

そして、doubleは32ビットでアトミックに記述できるという知識。

いいえ、それは保証されていません:

12.5変数参照のアトミシティ

次のデータ型の読み取りと書き込みはアトミックである必要があります:bool、char、byte、sbyte、short、ushort、uint、int、float、および参照型。さらに、前のリストの基になる型を持つ列挙型の読み取りと書き込みもアトミックである必要があります。long、ulong、double、decimalなどの他のタイプの読み取りと書き込み、およびユーザー定義のタイプは、アトミックである必要はありません。

(強調鉱山)

32ビットまたは64ビットのdoubleについては保証されません。2つのstrcutダブルで構成されるとさらに問題が発生します。あなたはあなたの戦略を再考するべきです。

于 2012-06-12T09:26:06.630 に答える
1

を(乱用)使用できますReaderWriterLockSlim

書き込み時に読み取りロックを取得します(ライター間に競合がないと言うため)。また、クローン作成時に書き込みロックを取得します。

本当に代替手段がない限り、私がこれを行うかどうかはわかりません。これを維持する人にとっては混乱を招く可能性があります。

于 2012-06-12T09:32:02.000 に答える
1

個々の配列要素または個々の構造体フィールドの読み取りと書き込みは、通常、独立しています。1つのスレッドが特定の構造体インスタンスの特定のフィールドを書き込んでいるときに、他のスレッドがその同じフィールドにアクセスしようとしない場合、構造体の配列は、上記の条件を強制するロジックを超えてロックする必要がなく、暗黙的にスレッドセーフになります。

あるスレッドが別のスレッドが書き込みを行っている間に読み取りを試みる可能性がdoubleあるが、2つのスレッドが同時に書き込みを試みる可能性がない場合は、読み取りが表示されないようにするためのさまざまなアプローチがあります。部分的に書き込まれた値。まだ言及されていないものの1つは、int64フィールドを定義し、カスタムメソッドを使用してそこに値を読み書きdoubleすることです(ビット単位で変換し、必要に応じて使用Interlockedします)。

別のアプローチは、changeCount配列スロットごとに変数を設定することです。この変数は、構造体が書き込まれる前に2つのLSBが「10」になり、Interlocked.Incrementその後に2になるようにインクリメントされます(以下の注を参照)。コードが構造体を読み取る前に、書き込みが進行中であるかどうかを確認する必要があります。そうでない場合は、読み取りを実行し、書き込みが開始または発生していないことを確認する必要があります(読み取りの開始後に書き込みが発生した場合は、最初にループバックします)。コードが読み取りを行うときに書き込みが進行中の場合は、共有ロックを取得し、書き込みがまだ進行中であるかどうかを確認し、進行中の場合は、インターロック操作を使用してロックのLSBを設定する必要がchangeCountありMonitor.Waitます。Interlocked.Increment構造体を記述したコードは、LSBが設定されたことに気付くはずです。Pulseロック。メモリモデルにより、単一スレッドによる読み取りが順番に処理され、単一スレッドによる書き込みが順番に処理されることが保証され、一度に1つのスレッドのみが配列スロットを書き込もうとする場合、このアプローチは次のようになります。Interlocked非競合の場合、マルチプロセッサのオーバーヘッドを単一の操作に制限します。この種のコードを使用する前に、メモリモデルによって暗示されるものと暗示されないものについての規則を注意深く検討する必要があることに注意してください。これは、注意が必要な場合があるためです。

ところで、各配列要素を構造体ではなくクラス型にしたい場合は、さらに2つのアプローチをとることができます。

  1. 不変のクラスタイプを使用し、要素を更新するときはいつでも`Interlocked.CompareExchange`を使用します。使用するパターンは次のとおりです。
      MyClass oldVal、newVal;
      行う
      {{
        oldVal = theArray [subscript];
        newVal = new MyClass(oldVal.this、oldVal.that + 5); //またはその他の変更
      } while(Threading.Interlocked.CompareExchange(theArray [subscript]、newVal、oldVal)!= oldVal);
    
    このアプローチでは、配列要素の論理的に正しいアトミック更新が常に生成されます。配列要素が読み取られてから更新されるまでの間に、他の何かが値を変更した場合、 `CompareExchange`は配列要素に影響を与えず、コードはループバックして再試行します。このアプローチは、競合がない場合でも適切に機能しますが、更新のたびに新しいオブジェクトインスタンスを生成する必要があります。ただし、多くのスレッドが同じ配列スロットを更新しようとしていて、 `MyClass`のコンストラクターの実行にかなりの時間がかかる場合は、コードがスラッシュされ、新しいオブジェクトが繰り返し作成され、それらが廃止されていることがわかります。それらを保存できる時間。コードは常に前進しますが、必ずしも迅速に進むとは限りません。
  2. 可変クラスを使用し、クラスオブジェクトを読み書きしたいときはいつでもクラスオブジェクトをロックします。このアプローチでは、何かが変更されたときに新しいクラスオブジェクトインスタンスを作成する必要がなくなりますが、ロックすると独自のオーバーヘッドが追加されます。読み取りと書き込みの両方をロックする必要があるのに対し、不変クラスのアプローチでは、書き込みに使用する必要があるのは「Interlocked」メソッドのみであることに注意してください。

構造体の配列はクラスオブジェクトの配列よりも優れたデータホルダーであると思う傾向がありますが、どちらのアプローチにも利点があります。

于 2012-06-12T15:43:42.763 に答える
0

さて、昼食時にこれについて考えました。

ここに2つ、おそらく3つの解決策があります。

最初の重要な注意:NoSとLastへの並列書き込みを独立して実行している2つのサービスがあるため、私のユースケースでは不変のアイデアは機能しません。これは、新しい参照が一方のサービスによって作成されている間、もう一方のサービスが同じことを行わないようにするために、これら2つのサービス間に同期ロジックの追加レイヤーが必要になることを意味します。古典的な競合状態の問題なので、この問題には間違いなく適切ではありません(ただし、各ダブルの参照を取得してそのようにすることはできますが、その時点でばかげています)

解決策1 キャッシュレベル全体のロック。たぶん、スピンロックを使用して、すべての更新とスナップショット(memcpyを使用)をロックするだけです。これは最も単純で、私が話しているボリュームにはおそらく完全に問題ありません。

解決策 2doubleへのすべての書き込みで、インターロックされた書き込みを使用するようにします。スナップショットを作成する場合は、インターロックされた読み取りを使用して配列と各値を繰り返し、コピーにデータを入力します。これにより、構造体ごとにティアリングが発生する可能性がありますが、ダブルスはそのままで、データを継続的に更新しているため、最新の概念は少し抽象的です。

解決策3 これが機能するとは思わないが、すべてのdoubleへのインターロックされた書き込みについてはどうでしょうか。その後、memcopyを使用してください。ダブルスが破れるかどうかはわかりませんが?(構造体レベルでのティアリングは気にしないことを忘れないでください)。

解決策3が機能する場合、私はその最高のパフォーマンスを推測しますが、そうでない場合、私は解決策1にもっと傾いています。

于 2012-06-12T13:16:22.423 に答える