59

これは 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' 関数のスコープ内で equals 演算子によって行われる設定ですか、それとも '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 を更新します」)。オブジェクトのフィールドではなく、オブジェクト自体をロックしたいのはそのためです。このクラスのすべてのデバイスが同じ動作をするわけではありません。

4

9 に答える 9

17

あなたのアプローチがうまくいけば、並行プログラミングはかなり簡単でしょう。しかし、そうではありません。たとえば、タイタニック号を沈める氷山は、これを行うクラスのクライアントです。

objectRef.MyProperty += 1;

リードモディファイライトの競合は非常に明白ですが、もっと悪いものもあります。プロパティを不変にする以外に、プロパティをスレッドセーフにするためにできることは絶対にありません。頭痛に対処する必要があるのはあなたのクライアントです。その種の責任を、それを正しく行う可能性が最も低いプログラマーに委任することを余儀なくされるのは、並行プログラミングのアキレス腱です。

于 2009-02-03T01:36:29.713 に答える
5

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

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

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

例として...

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();
于 2009-02-03T02:05:53.697 に答える
1

マルチスレッドの利点は、物事がどの順序で発生するかわからないことです。1 つのスレッドに何かを設定すると、最初に発生する場合もあれば、get の後に発生する場合もあります。

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

于 2009-02-03T00:24:04.383 に答える
0

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

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

于 2009-02-03T01:23:52.533 に答える
0

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

このコンテキストで興味深いかもしれない C# の不変モデル クラスに関する記事を書きました。

于 2012-10-15T08:51:19.003 に答える
-2

C# のロックは、他の言語と同じロックの問題を抱えていませんか?

例えば

var someObj = -1;

// Thread 1

if (someObj = -1)
    lock(someObj)
        someObj = 42;

// Thread 2

if (someObj = -1)
    lock(someObj)
        someObj = 24;

これにより、両方のスレッドが最終的にロックを取得して値を変更するという問題が発生する可能性があります。これにより、いくつかの奇妙なバグが発生する可能性があります。ただし、必要でない限り、オブジェクトを不必要にロックしたくはありません。この場合、二重チェック ロックを検討する必要があります。

// Threads 1 & 2

if (someObj = -1)
    lock(someObj)
        if(someObj = -1)
            someObj = {newValue};

心に留めておくべきことがあります。

于 2016-02-19T18:59:15.520 に答える