11

マルチスレッド プログラミングについては、学ぶべきことがたくさんあるようで、少しばかり気が引けます。

私の現在のニーズでは、メソッドが終了する前に別のスレッドから再度呼び出されないようにしたいだけです。私の質問は次のとおりです。

これは、メソッドをスレッドセーフにする適切な (安全な) 方法ですか?

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

        doingWork = true;

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

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


編集:シナリオに関する詳細情報:

  • Foo のインスタンスは 1 つだけです。

  • Foo.DoWork() は、System.Timers.Timer の Elapsed イベントで ThreadPool スレッドから呼び出されます。

  • 通常、 Foo.DoWork() は次に呼び出されるまでに何百年も前に終了しますが、長く実行され、終了する前に再度呼び出されるわずかな可能性のためにコーディングしたいと思います。


(また、この質問に言語に依存しないタグを付けることができるかどうかを確認できるほど賢くないので、タグを付けていません。賢明な読者は、該当する場合は自由にタグを付けてください。)

4

4 に答える 4

13

コードはスレッドセーフではありません。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);
            }
        }
    }
}
于 2011-07-29T22:13:36.123 に答える
3

再入可能性は、マルチスレッドとは何の関係もありません。

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

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

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

あなたの編集された質問は、このセクション全体があなたにとって無関係であることを示しています.
とにかく読むべきでしょう。


複数のスレッドから同時に呼び出された場合に、メソッドが一度に 2 回実行されないようにするために、 (編集:)排他性を探しているかもしれません。
あなたのコードは排他的ではありません。2 つのスレッドが一度にメソッドを実行し、両方が同時にifステートメントを実行する場合、両方とも を通過しif、両方ともフラグを設定して、両方doingWorkともメソッド全体を実行します。

lockそのためには、キーワードを使用します。

于 2011-07-29T22:04:08.360 に答える
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;}}
}

field の代わりに使用できるようになりましたが、ロックの使用回数が増えるため、ロックに費やす時間が長くなります。

または、完全なフェンス アプローチを使用することもできます (優れたスレッディング ブックの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 ベースの doingWork フィールドの周りのクラスを使用して行うこともできると思います。

于 2011-07-29T22:33:42.620 に答える
0

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

代わりにバリアの使用を検討したい場合があります。それはすべての作業を行います。これは、再入可能コードを制御する標準的な方法です。また、その作業を一度に実行するスレッドの量を制御することもできます (1 つ以上を許可する場合)。

于 2011-07-29T22:02:26.873 に答える