2011-07-29 21 views
9

マルチスレッドプログラミングについて学ぶことはたくさんあるようですが、それはやや威圧的です。私の現在のニーズにメソッド全体をスレッドセーフにする最も簡単な方法は?

、私はちょうどそれが終了する前に再び別のスレッドから呼び出されるメソッドから保護したい、と私の質問は:

これは作るための適切な(安全な)方法ですメソッドはスレッドセーフですか?

class Foo 
{ 
    bool doingWork; 
    void DoWork() 
    { 
     if (doingWork) // <- sophistocated thread-safety 
      return;  // <- 

     doingWork = true; 

     try 
     { 
      [do work here] 
     } 
     finally 
     { 
      doingWork = false; 
     } 
    } 
} 

それが十分でない場合は、これを実現する最も簡単な方法は何ですか?


EDIT:シナリオについての詳細情報:

  • がfooのインスタンスが1つしかない

  • Foo.DoWorkは()にThreadPoolのスレッドから呼び出されます

  • Elapsed System.Timers.Timerのイベントです。

  • 通常Foo.DoWork()が呼び出され の次の時間の前に長い年月を終えるが、私はそれは、長い を実行し、終了する前に再び呼び出されることにスリムなチャンスをコードすることになるでしょう。


(私もこの質問は、言語に依存しないタグを付けることができれば、必ずできるほど賢くないので、私はそうではありません。悟りを開いた読者は、その該当する場合に行うこと自由に感じています。)

+0

Foo型のオブジェクトは、各スレッドに対してインスタンス化されていますか、複数のスレッドで共有されていますか? – NotMe

+0

複数のスレッドがありますか? – sll

+0

リエントラントをサポートしたり、コードを設計したりしないようにしてください。それが起こったときにただ救済することは、正しい解決策ではないようです。しかし、あなたの編集は、糸の安全性が実際には再突入ではなく問題であることを明確にしています。 –

答えて

8

コードはスレッドセーフではありません。代わりにlockキーワードを使用してください。あなたの現在のコードで

:次のスレッドが伝わってくるとき

if (doingWork) 
     return; 

    // A thread having entered the function was suspended here by the scheduler. 

    doingWork = true; 

、それはまた、関数を入力します。

これは、lock構成が使用される理由です。このコードは、元よりもやや異なる意味を持っていることを

class Foo 
{ 
    object lockObject = new object; 
    void DoWork() 
    { 
     lock(lockObject) 
     { 
      [do work here] 
     } 
    } 
} 

注:これは、基本的にはあなたのコードと同じですが、途中で中断されたスレッドのリスクなしに行います。このコードでは、2番目のスレッドが待機してから作業を開始します。元のコードで2番目のスレッドがただちに中止されました。元のコードに近づくために、C#lockステートメントは使用できません。基礎となるMonitor構築物を直接使用する必要があります。

class Foo 
{ 
    object lockObject = new object; 
    void DoWork() 
    { 
     if(Monitor.TryEnter(lockObject)) 
     { 
      try 
      { 
       [do work here] 
      } 
      finally 
      { 
       Monitor.Exit(lockObject); 
      } 
     } 
    } 
} 
+0

私は実際に中断する必要はないので、ロック方法はうまくいくはずです。甘い - それは簡単です!ありがとう。 –

+0

[バレンタイン](http://stackoverflow.com/questions/6879468/simplest-way-to-make-a-whole-method-thread-safe/6879698#6879698)にはロックだけで中止を行う方法があります)。あなたのコメントに興味があります。 –

0

http://msdn.microsoft.com/en-us/library/system.threading.barrier.aspx

代わりにバリアを使用してみたいと思うかもしれませんが、それはすべてあなたの仕事です。これは、リエントラントコードを制御する標準的な方法です。また、一度にその作業を行っているスレッドの量を制御することもできます(1以上を許可する場合)。

+0

それは私にとって必要以上に複雑に思えます。私は本当に自分のコードスニペットが意味するものを達成したいだけです。再入力すると飛び出します。 –

+0

障壁の参加者数を確認します(0より大きい場合)。参加者を追加して、参加者の最後に参加して作業します。バリアはスレッドセーフであることを意図しています。 –

+0

チェックを行い、参加者をアトミックに追加できますか?もしそうなら、どうですか? –

3

リエントラントはマルチスレッドとは関係ありません。

リエントラント方式とは、同じスレッド上で自身の内部から呼び出される方法です。
たとえば、メソッドがイベントを発生させ、そのイベントを処理するクライアントコードがイベントハンドラ内でメソッドを再度呼び出す場合、そのメソッドはリエントラントです。
このメソッドを再入可能から保護するということは、内部から呼び出すと、anyhtingも例外もスローされないことを保証することを意味します。

あなたのコードは、すべてが同じスレッド上にある限り、同じオブジェクトインスタンス内の再入場から保護されています。

[do work here]は、外部コードを実行することができない限り(例えば、イベントを発生させることによって、または他のものからデリゲートやメソッドを呼び出すことによって)、最初は再入可能ではありません。

編集した質問は、このセクション全体があなたとは無関係であることを示しています。
あなたはおそらくそれをとにかく読むべきです。同時に複数のスレッドによって呼び出された場合、このメソッドは二回、一度に実行されないことを保証する独占 –を探して:


あなたは(あるEDIT)であってもよいです。
コードは排他的ではありません。 2つのスレッドが一度にメソッドを実行し、両方ともifステートメントを一度に実行すると、ifを通過し、次に両方ともdoingWorkフラグを設定し、両方ともメソッド全体を実行します。

これを行うには、lockキーワードを使用します。

+0

タグでマルチスレッドだから1つだけ仮定することができます。 –

+1

1つは_what_と仮定できますか?質問はマルチスレッドとは関係ありません。 **編集**:さて、そうです。 – SLaks

+0

確かに、別のスレッドから再度呼び出されたメソッドについてしか考えていませんでした。これを指摘してくれてありがとう - 私はそのことを明確にするために質問を編集しました。しかし、私はこの言葉がマルチスレッディングのシナリオにも適用できると信じています。そうでなければ、メソッドが既に実行中に別のスレッドから呼び出されることに対処できるようにする必要があるときに、どのように呼び出すでしょうか? –

2

あなたは簡単コードをしたいし、あまりにも多くのパフォーマンスについて気にしないならば、それはもし

}

class Foo 
{ 
    bool doingWork; 
object m_lock=new object(); 
    void DoWork() 
    { 
     lock(m_lock) // <- not sophistocated multithread protection 
{ 
     if (doingWork) 
      return;  
     doingWork = true; 
} 


     try 
     { 
      [do work here] 
     } 
     finally 
     { 
lock(m_lock) //<- not sophistocated multithread protection 
{ 
      doingWork = false; 
} 
     } 
    } 

と同じくらい簡単にすることができますあなたは少しロックを解除したい、このようなスレッドセーフなプロパティを作成することができます:

public bool DoingWork 
{ 
get{ lock(m_Lock){ return doingWork;}} 
set{lock(m_lock){doingWork=value;}} 
} 

これで、代わりにフィールドを使用できますが、ロックに費やす時間が長くなり、ロックの使用回数が増えます。

それとも、(偉大なスレッド帳Joseph Albahari online threadingから)

class Foo 
{ 
    int _answer; 
    bool _complete; 

    void A() 
    { 
    _answer = 123; 
    Thread.MemoryBarrier(); // Barrier 1 
    _complete = true; 
    Thread.MemoryBarrier(); // Barrier 2 
    } 

    void B() 
    { 
    Thread.MemoryBarrier(); // Barrier 3 
    if (_complete) 
    { 
     Thread.MemoryBarrier();  // Barrier 4 
     Console.WriteLine (_answer); 
    } 
    } 
} 

をフルフェンス・アプローチを使用することができ、彼は完全なフェンスがロックの文よりも速く2倍であることを述べています。場合によっては、MemoryBarrier()への不要な呼び出しを削除することでパフォーマンスを向上させることができますが、lockを使用すると、シンプルで、はっきりとしたエラーが発生しにくくなります。

私はこれも、Interlockedクラスのint based doingWorkフィールドを使って行うことができると信じています。

+0

あなたのlock()(workingWorkへのロックアクセス)とAnders 'lock()メソッド(作業を行っているコードへのアクセスをロックしています)の間に顕著な違いはありますか? –

+0

論理的な違いがあります。私が彼の答えの方法から見ることができるように、リエントラントであり、それ以上のコールはキューに入れられます。私のコードでは、あるスレッドで実行されている場合、他のスレッドがこのメソッドをキューに入れたくないと仮定します。私の経験では、ボタンをダブルクリックして複数のサーバー要求をユーザーが起動できるようにしたくない場合など、私のアプローチがしばしば好まれます。 PS:ロックを使って行うことはできないと言っているが、私はロックを使ってやる。 Monitor.TryEnterはより良いパフォーマンスになるかもしれませんが、日々のニーズにはもう少し進んでいます。重要なパフォーマンスコードのために残しておいてください。 –

+0

ああ、私は、クール参照してください。どのようにそれは、アンダース好きですか? :) –

関連する問題