web-dev-qa-db-ja.com

get / setを使用したC#スレッドセーフティ

これはC#の詳細な質問です。

オブジェクトを持つクラスがあり、そのオブジェクトがロックで保護されているとします:

Object mLock = new Object();
MyObject property;
public MyObject MyProperty {
    get {
         return property;
    }
    set { 
         property = value; 
    }
}

ポーリングスレッドがそのプロパティをクエリできるようにしたいのです。また、スレッドがそのオブジェクトのプロパティをときどき更新するようにし、ユーザーがそのプロパティを更新できる場合があり、ユーザーはそのプロパティを表示できるようにしたいと考えています。

次のコードはデータを適切にロックしますか?

Object mLock = new Object();
MyObject property;
public MyObject MyProperty {
    get {
         lock (mLock){
             return property;
         }
    }
    set { 
         lock (mLock){
              property = value; 
         }
    }
}

「適切に」とは、私が電話したい場合

MyProperty.Field1 = 2;

または何でも、更新中にフィールドはロックされますか? 「get」関数のスコープ内で等号演算子によって行われる設定、または「get」関数(およびロック)が最初に終了し、次に設定、次に「set」が呼び出されるため、バイパスロック?

編集:これは明らかにトリックをしないので、何をしますか?私は次のようなことをする必要がありますか?

Object mLock = new Object();
MyObject property;
public MyObject MyProperty {
    get {
         MyObject tmp = null;
         lock (mLock){
             tmp = property.Clone();
         }
         return tmp;
    }
    set { 
         lock (mLock){
              property = value; 
         }
    }
}

これは多かれ少なかれ、コピーにのみアクセスできることを確認するだけです。つまり、2つのスレッドが同時に「get」を呼び出すようにすると、それぞれが同じ値のField1で始まります(右?)。意味のあるプロパティで読み取りと書き込みのロックを行う方法はありますか?または、データ自体ではなく、関数のセクションをロックするだけに制限する必要がありますか?

この例が理にかなっているように:MyObjectはステータスを非同期に返すデバイスドライバーです。シリアルポート経由でコマンドを送信すると、デバイスはそれらのコマンドに自分の甘い時間に応答します。現在、ステータスをポーリングするスレッド(「まだそこにいますか?コマンドを受け入れますか?」)、シリアルポートでの応答を待機するスレッド(「ステータス文字列2を取得しました、すべて問題ありません」)があります。 )、他のコマンド(「ユーザーはこのことをしたい」)を受け取り、ドライバーからの応答を投稿するUIスレッド(「今やったことがあります。それでUIを更新します」)。そのため、オブジェクトのフィールドではなく、オブジェクト自体をロックしたいのです。これは膨大な数のロックa、bであり、このクラスのすべてのデバイスが同じ動作をするわけではなく、一般的な動作なので、ロックを個別に設定する場合は多くの個別のダイアログをコーディングする必要があります。

49
mmr

いいえ、コードはMyPropertyから返されたオブジェクトのメンバーへのアクセスをロックしません。 MyProperty自体のみをロックします。

実際の使用例は、実際には2つの操作が1つにロールされ、これはほぼ次のようになります。

_// object is locked and then immediately released in the MyProperty getter
MyObject o = MyProperty;

// this assignment isn't covered by a lock
o.Field1 = 2;

// the MyProperty setter is never even called in this example
_

一言で言えば-2つのスレッドがMyPropertyに同時にアクセスすると、ゲッターはオブジェクトを最初のスレッドに返すまで2番目のスレッドを短時間ブロックしますbut次に、オブジェクトを2番目のスレッドにも返します。両方のスレッドは、オブジェクトへの完全なロック解除されたアクセス権を持ちます。

質問の詳細に応じて編集

私はまだあなたが達成しようとしていることを100%確信していませんが、オブジェクトへのアトミックアクセスが必要な場合は、オブジェクト自体に対する呼び出しコードロックがありませんか?

_// quick and dirty example
// there's almost certainly a better/cleaner way to do this
lock (MyProperty)
{
    // other threads can't lock the object while you're in here
    MyProperty.Field1 = 2;
    // do more stuff if you like, the object is all yours
}
// now the object is up-for-grabs again
_

理想的ではありませんが、オブジェクトへのすべてのアクセスがlock (MyProperty)セクションに含まれている限り、このアプローチはスレッドセーフになります。

39
LukeH

アプローチが機能する場合、並行プログラミングは非常に簡単です。しかし、そうではありません。タイタニックを沈める氷山は、たとえば、あなたのクラスのクライアントです。

objectRef.MyProperty += 1;

読み取り-変更-書き込みの競合は非常に明白であり、さらに悪いものもあります。プロパティを不変にする以外に、プロパティをスレッドセーフにするためにできることは絶対にありません。頭痛に対処する必要があるのはクライアントです。そのような責任をプログラマーに委任することを余儀なくされるのは、並行プログラミングのアキレス腱です。

14
Hans Passant

他の人が指摘したように、ゲッターからオブジェクトを返すと、誰がいつオブジェクトにアクセスするかを制御できなくなります。やりたいことをするには、オブジェクト自体の内部にロックを設定する必要があります。

おそらく私は全体像を理解していないかもしれませんが、あなたの説明に基づいて、必ずしも個々のフィールドごとにロックを持っている必要があるとは思えません。ゲッターとセッターを介して単純に読み取りおよび書き込みが行われるフィールドのセットがある場合、これらのフィールドの単一のロックで逃げることができます。この方法でスレッドの操作を不必要にシリアル化する可​​能性があることは明らかです。ただし、説明に基づいて、オブジェクトに積極的にアクセスしているようにも聞こえません。

スレッドを使用してデバイスステータスをポーリングする代わりに、イベントを使用することもお勧めします。ポーリングメカニズムを使用すると、スレッドがデバイスを照会するたびにロックがヒットします。イベントメカニズムを使用すると、ステータスが変更されると、オブジェクトはリスナーに通知します。その時点で、「ポーリング」スレッド(ポーリングではなくなります)が起動し、新しいステータスを取得します。これははるかに効率的です。

例として...

public class Status
{
    private int _code;
    private DateTime _lastUpdate;
    private object _sync = new object(); // single lock for both fields

    public int Code
    {
        get { lock (_sync) { return _code; } }
        set
        {
            lock (_sync) {
                _code = value;
            }

            // Notify listeners
            EventHandler handler = Changed;
            if (handler != null) {
                handler(this, null);
            }
        }
    }

    public DateTime LastUpdate
    {
        get { lock (_sync) { return _lastUpdate; } }
        set { lock (_sync) { _lastUpdate = value; } }
    }

    public event EventHandler Changed;
}

「ポーリング」スレッドは次のようになります。

Status status = new Status();
ManualResetEvent changedEvent = new ManualResetEvent(false);
Thread thread = new Thread(
    delegate() {
        status.Changed += delegate { changedEvent.Set(); };
        while (true) {
            changedEvent.WaitOne(Timeout.Infinite);
            int code = status.Code;
            DateTime lastUpdate = status.LastUpdate;
            changedEvent.Reset();
        }
    }
);
thread.Start();
4
Matt Davis

あなたの例のロックスコープは間違った場所にあります-コンテナではなく 'MyObject'クラスのプロパティのスコープにある必要があります。

MyObject myオブジェクトクラスを使用して、1つのスレッドが書き込みたいデータと、別のスレッド(UIスレッド)が読み取りたいデータを含める場合、セッターはまったく必要なく、一度構築するだけです。

また、プロパティレベルでロックを配置することがロックの粒度の書き込みレベルであるかどうかも考慮してください。トランザクションの状態を表すために複数のプロパティが書き込まれる可能性がある場合(例:合計注文数と合計重量)、MyObjectレベルでロックすることをお勧めします(つまり、lock(myObject.SyncRoot).. )

2
headsling

投稿したコード例では、getは実行されません。

より複雑な例:

_MyProperty.Field1 = MyProperty.doSomething() + 2;
_

そしてもちろん、あなたがやったことを前提としています:

_lock (mLock) 
{
    // stuff...
}
_

doSomething()では、すべてのロック呼び出しがnotでオブジェクト全体の同期を保証するのに十分です。 doSomething()関数が戻るとすぐにロックが失われ、追加が行われ、割り当てが行われ、再びロックされます。

または、別の方法で記述すると、ロックが完全に実行されないふりをして、1行につき1つの操作で「マシンコード」のように書き換えることができます。

_lock (mLock) 
{
    val = doSomething()
}
val = val + 2
lock (mLock)
{
    MyProperty.Field1 = val
}
_
1
SoapBox

マルチスレッドの利点は、どの順序で発生するかわからないことです。1つのスレッドに何かを設定すると、最初に発生する可能性があり、取得後に発生する可能性があります。

で投稿したコードは、読み取りおよび書き込み中にメンバーをロックします。値が更新されるケースを処理する場合は、おそらく events などの他の形式の同期を検討する必要があります。 (自動/手動バージョンを確認してください)。次に、「ポーリング」スレッドに値が変更され、再読み込みの準備ができていることを伝えることができます。

1
OJ.

編集したバージョンでは、MyObjectを更新するスレッドセーフな方法はまだ提供されていません。オブジェクトのプロパティの変更は、同期/ロックされたブロック内で行う必要があります。

これを処理するために個別のセッターを作成できますが、フィールド数が多いためこれが難しいことを示しました。確かにその場合(そして、まだこれを評価するのに十分な情報を提供していない場合)、1つの選択肢はリフレクションを使用するセッターを書くことです。これにより、フィールド名を表す文字列を渡すことができ、フィールド名を動的に検索して値を更新できます。これにより、任意の数のフィールドで機能する単一のセッターを使用できます。これはそれほど簡単でも効率的でもありませんが、多数のクラスとフィールドを扱うことができます。

0
jdigital

オブジェクトを取得/設定するためのロックを実装しましたが、オブジェクトをスレッドセーフにしていません。これは別の話です。

このコンテキストで興味深いかもしれないC#の不変モデルクラスに関する記事を書いた: http://rickyhelgesson.wordpress.com/2012/07/17/mutable-or-immutable-in-a-parallel -world /

0
Ricky Helgesson