web-dev-qa-db-ja.com

c#-揮発性キーワードの使用とロック

揮発性を使用しましたが、必要かどうかはわかりません。私の状況では、ロックが多すぎると確信していました。このスレッド(Eric Lippertのコメント)を読むと、私のvolatileの使用に不安を感じます: いつvolatileキーワードをc#で使用する必要がありますか?

私の変数は、この変数に同時にアクセス/変更できるマルチスレッドコンテキストで使用されているため、揮発性を使用しましたが、問題なく追加を失うことができます(コードを参照)。

ミスアライメントが発生しないように「揮発性」を追加しました。変数の32ビットと、別のスレッドからの途中での書き込みによって2に分割される可能性がある別のフェッチで32ビットのみを読み取ります。

私の以前の仮定(以前のステートメント)は本当に起こり得ないのですか?そうでない場合でも、「揮発性」の使用法はまだ必要ですか(オプションプロパティの変更はどのスレッドでも発生する可能性があります)。

2つの最初の答えを読んだ後。コードの記述方法は、並行性のためにインクリメントが失われた場合は重要ではありません(2つのスレッドからのインクリメントが必要ですが、同時実行性のために結果が1つだけインクリメントされます)。変数「_actualVersion」がインクリメントされます。

参考までに、これは私が使用しているコードの一部です。アプリケーションがアイドル状態のときにのみ、保存アクション(ディスクへの書き込み)を報告します。

public abstract class OptionsBase : NotifyPropertyChangedBase
{
    private string _path;

    volatile private int _savedVersion = 0;
    volatile private int _actualVersion = 0;

    // ******************************************************************
    void OptionsBase_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        _actualVersion++;
        Application.Current.Dispatcher.BeginInvoke(new Action(InternalSave), DispatcherPriority.ApplicationIdle);
    }

    // ******************************************************************
    private void InternalSave()
    {
        if (_actualVersion != _savedVersion)
        {
            _savedVersion = _actualVersion;
            Save();
        }
    }

    // ******************************************************************
    /// <summary>
    /// Save Options
    /// </summary>
    private void Save()
    {
        using (XmlTextWriter writer = new XmlTextWriter(_path, null))
        {
            writer.Formatting = Formatting.Indented;
            XmlSerializer x = new XmlSerializer(this.GetType());

            x.Serialize(writer, this);
            writer.Close();
        }
    }
30
Eric Ouellet

揮発性を使用しましたが、必要かどうかはわかりません。

この点についてはっきりさせておきます。

C#でのvolatileの意味が100%明確でない場合は、使用しないでくださいこれは鋭いツールです専門家のみが使用することを意図しています。 2つのスレッドが2つの異なるvolatileフィールドを読み書きしているときに、メモリアクセスの可能なすべての並べ替えが弱いメモリモデルアーキテクチャによって許可されるものを説明できない場合、volatileを安全に使用するのに十分な知識がなく、間違いを犯します。ここで行われ、非常に壊れやすいプログラムを記述します。

私の状況ではロックが過剰になると確信していました

まず、最善の解決策は単にそこに行かないにすることです。メモリを共有しようとするマルチスレッドコードを記述しない場合は、ロックを心配する必要はありません。

メモリを共有するマルチスレッドコードを記述する必要がある場合、ベストプラクティスはalwaysロックを使用することです。ロックが過剰になることはほとんどありません。競合しないロックの価格は、およそ10ナノ秒です。 10ナノ秒の追加がユーザーに影響を与えると本当に言っていますか?もしそうなら、あなたは非常に、非常に高速なプログラムと非常に高い基準を持つユーザーを持っています。

ロック内のコードが高価な場合、競合するロックの価格はもちろん任意に高くなります。 ロック内で高価な作業を行わないでくださいなので、競合の可能性は低くなります。

実証済み競合を解消することで解決できないロックのパフォーマンス問題がある場合のみ、低ロックソリューションの検討を開始する必要があります。

不整合が発生しないように「揮発性」を追加しました。変数の32ビットと、別のスレッドからの途中の書き込みによって2つに分割される可能性がある別のフェッチで32ビットのみを読み取ります。

この文は、マルチスレッドコードの記述を今すぐ停止する必要があることを示しています。マルチスレッドコード、特にローロックコードは専門家専用です。マルチスレッドコードの記述を再開する前に、システムが実際にどのように機能するかを理解する必要があります。主題についての良い本を手に入れ、一生懸命勉強してください。

あなたの文は無意味です:

まず、整数はすでに32ビットのみです。

第2に、intアクセスは仕様によってアトミックであることが保証されています。原子性が必要な場合は、すでにそれを持っています。

第3に、はい。揮発性アクセスが常にアトミックであることは事実ですが、C#がすべての揮発性アクセスをアトミックアクセスにするためではありません。むしろ、フィールドがすでにアトミックでない限り、C#はフィールドにvolatileを設定することを違法にします。

第4に、volatileの目的は、C#コンパイラ、ジッター、およびCPUが、弱いメモリモデルでプログラムの意味を変更する特定の最適化を行うのを防ぐことです。特に揮発性は++をアトミックにしません。 (私は静的アナライザーを作る会社で働いています。私は「揮発性フィールドでの不正な非アトミック操作」チェッカーのテストケースとしてあなたのコードを使用します。実世界のコードが満載されていると、とても役に立ちます。現実的な間違い;私たちは人々が書いたバグを実際に見つけていることを確認したいので、これを投稿していただきありがとうございます。)

実際のコードを見る:volatileは、Hansが指摘したように、コードを正しくするのにまったく不十分です。最善の方法は、前に言ったことですこれらのメソッドがメインスレッド以外のスレッドで呼び出されるのを許可しない。カウンターロジックが間違っているということは、あなたの心配事の最低限であるべきです。 シリアル化中に別のスレッドのコードがオブジェクトのフィールドを変更している場合に、シリアル化スレッドが安全になる理由は何ですか?最初に心配する必要がある問題です。

63
Eric Lippert

揮発性は、このコードを安全にするためにひどく不適切です。 Interlocked.Increment()およびInterlocked.CompareExchange()で低レベルのロックを使用できますが、Save()がスレッドセーフであると想定する理由はほとんどありません。確かに、ワーカースレッドによって変更されているオブジェクトを保存しようとしているように見えます。

ここでは、バージョン番号を保護するだけでなく、シリアル化中にオブジェクトが変更されないようにするために、lockを使用することを強く示しています。これを行わないことで得られる破損した保存は、非常にまれであるため、問題のデバッグを行うことはできません。

13
Hans Passant

Joe Albahariの優れた スレッドとロックへの投稿 によると、これは同等に優れた本のC#5.0 In A Nutshellからのものです here volatileキーワードが使用されている場合でも、書き込みステートメントに続いて読み取りステートメントを並べ替えることができます。

さらに下に、彼はこのトピックに関するMSDNドキュメントが正しくないと言い、揮発性キーワードを完全に回避するための強い主張があることを示唆しています。彼は、あなたがたまたま関係する微妙さを理解したとしても、将来の開発者もそれを理解するであろうと指摘します。

したがって、ロックの使用はより「正しい」だけでなく、より理解しやすく、複数の更新ステートメントをアトミックな方法でコードに追加する新機能を簡単に追加できます-揮発性でもフェンスクラスでもない MemoryBarrier は実行でき、非常に高速であり、経験の浅い開発者が微妙なバグを引き起こす可能性がはるかに少ないため、保守が非常に簡単です。

3
Bob Bryan

Volatileを使用するときに変数を2つの32ビットフェッチに分割できるかどうかについてのステートメントに関しては、Int32よりも大きなものを使用している場合、これは可能性があります。

したがって、Int32を使用している限り、あなたが述べていることに問題はありません。

ただし、提案されたリンクを読んだように、揮発性は弱い保証しか与えないため、安全のためにロックをお勧めします。今日のマシンには複数のCPUがあり、揮発性は別のCPUがスイープしないことを保証しない予期しないことをします。

[〜#〜]編集[〜#〜]

Interlocked.Incrementの使用を検討しましたか?

1
idipous

InternalSave()メソッドでの比較と割り当ては、volatileキーワードの有無にかかわらず、スレッドセーフではありません。ロックの使用を避けたい場合は、フレームワークの Interlocked クラスのコードでCompareExchange()およびIncrement()メソッドを使用できます。

0
user2847824