3

セマフォの使用を共有する複数のスレッドがあります。スレッド A は (ロックを使用して) セマフォを保持し、スレッド B と C は同じセマフォで待機しています (これもロックを使用)。スレッドはグローバル変数などを共有します。

スレッド B をシャットダウンするために使用できる C# の手法はありますか? A にフラグを設定し、スレッド B にそのフラグをチェックさせて、セマフォの制御を取得したらすぐに終了させることができますが、スレッド A がセマフォをスレッド B に譲ることを許可する手法を知りません (そしてそれを取得します)。スレッド B が終了したときに戻る) スレッド C が制御を奪うリスクはありません。

この設計上の問題に対処する方法について何か提案はありますか? これに間違ってアプローチしている場合は、必要に応じてプログラムを書き直すことができます。

[編集] コメント投稿者から、私が間違った用語を使用していると指摘されました。コメンターは正しいです-私はクリティカルセクションを使用していますが、すべてが単一のプロセスで実行されていることを考えると、この例ではクリティカルセクションはより一般的な用語「セマフォ」と機能的に同等です。

【追記】詳しく知りたいという方がいらっしゃったので、こちらに。

コード A を実行する複数のスレッドが存在する可能性があります。コード B を実行するスレッドは 1 つだけです。

コード A:

private static Thread workerThread = null;

lock (lockObject)
{
    ... do some work ...

    if (...condition...)
    {
        if (workerThread != null)
        {
            // Kill the worker thread and continue only after it is dead.
            quitWorkerThread = true;
            // Wait for the thread to die.
            while (workerThread.IsAlive)
            {
                Thread.Sleep(50);
            }
            workerThread = null;
            quitWorkerThread = false;
        } // if (workerThread != null)
    } // if (...condition...)

    ... do some more work ...

    if (...condition...)
    {
        if (workerThread == null)
        {
            // Start the worker thread.
            workerThread = new Thread(WorkerThread);
            workerThread.Start();
        } // if (workerThread == null)
    } // if (...condition...)

    ... do even more work ...

} // lock (lockObject)

コード B:

private void WorkerThread()
{
    while (true)
    {
        if (quitWorkerThread)
        {
            return;
        }

        Thread.Sleep (2000);

        if (quitWorkerThread)
        {
            return;
        }

        lock(lockObject)
        {
            if (quitWorkerThread)
            {
                return;
            }
            ... do some work ...
        } // lock(lockObject)
    } // while (true)
} // WorkerThread

アーロンのソリューションの変形が私が使用するものになると思います。私は主に、もう少しエレガントな解決策が利用可能であることを望んでいましたが、このプロジェクトに関する他のすべてと同様に、それはすべてブルートフォースとコーナーケースだと思います:-(.

4

4 に答える 4

4

特定のスレッドに制御を渡す方法がないことは確かです。これは、あなたがやろうとしていることのようです。どのスレッドを次に実行するかを決定するのは Windows スケジューラ次第です。

A、B、C の 3 つのスレッドがある状況です。A にはロックがあり、B と C はそれを待っており、B が次に実行されることを保証する方法が必要です。

明らかな解決策は、複数のロックおよび/または同期プリミティブを使用することです。lockのセマンティクスをと組み合わせることができますManualResetEvent。スレッド C はイベントクリティカル セクションの両方を待機させますが、スレッド B はクリティカル セクションを待機するだけで済みます。通常の状況では、ロックを解放する直前にイベントを通知します。これにより、実行するスレッドの決定は OS に委ねられます。特殊なケースでは、イベントをまったく通知せず、C がまだブロックされている間、スレッド B を実行したままにします。

B が完了したら、イベントにシグナルを送って C を終了させます。


(テストされていない)例は次のとおりです。

// Main thread is Thread A
object myLock = new Object();
AutoResetEvent myEvent = new AutoResetEvent(false);
ManualResetEvent completedEvent = new ManualResetEvent(false);

ThreadPool.QueueUserWorkItem(s =>
{
    for (int i = 0; i < 10000; i++)
    {
        lock (myLock)
        {
            // Do some work
        }
    }
    completedEvent.Set();
});  // Thread B

ThreadPool.QueueUserWorkItem(s =>
{
    for (int i = 0; i < 10000; i++)
    {
        myEvent.WaitOne();
        lock (myLock)
        {
            // Do some work
        }
    }
});  // Thread C

// Main loop for thread A
while (true)
{
    lock (myLock)
    {
        // Do some work
        if (SomeSpecialCondition)
            break;
        else
            myEvent.Set();
    }
}

completedEvent.WaitOne(); // Wait for B to finish processing
if (SomeSpecialCondition) // If we terminated without signaling C...
    myEvent.Set();        // Now allow thread C to clean up

これにより、本質的に、スレッド C がいつ実行されるかをスレッド A が担当します。スレッド A と B は正常に競合しますが、スレッド C のイベントを通知するのはスレッド A 次第です。

于 2010-03-06T03:11:44.663 に答える
1

Monitor とMonitor.Pulseの使用を見てみましょう。これにより、特定のスレッドに譲る際に必要なものが正確に得られるわけではありませんが、スレッドとクリティカル セクションの間で制御を転送することができます。

あなたが解決しようとしている問題が何であるかは私にはわかりません。ReaderWriterLockSlimを使用して問題を解決できる場合もあります。

最後に、あなたのシナリオは、代わりに .NETイベントを使用する適切な場所のように思えます。

于 2010-03-06T03:32:48.947 に答える
1

(免責事項:あなたの唯一のユースケースがその特定のものである場合、ManualResetEventsを単に使用する@Aaronaughtのソリューションがおそらく最も簡単です。)

追加の免責事項のために編集: この概念を拡張したい場合は、デッドロックに非常に注意してください。

B が何かを実行したかどうかに関係なく C を動作させたいが、C が待機している場合は常に B を最初に動作させたい場合は、次の 1 つの簡単な解決策があります。

    object lockObject = new object();
    int WaitCounter = 0;

    void B()
    {
        System.Threading.Interlocked.Increment(ref WaitCounter);

        try
        {
            lock (lockObject)
            {
            }
        }
        finally
        {
            System.Threading.Interlocked.Decrement(ref WaitCounter);
        }
    }

    void C()
    {
        while (true)
        {
            // always attempt to yield to other threads first
            System.Threading.Thread.Sleep(0);
            lock (lockObject)
            {
                if (WaitCounter > 0)
                    continue;

                // ...

                return;
            }
        }
    }

余分なコードがたくさんありますが、並行性が簡単であると主張した人は誰もいません。:)

于 2010-03-06T03:37:15.463 に答える
1

Aaronaught の解決策は健全に見えますが、より単純な解決策は、もう少し共有された状態と単一のロックを使用することだと思います。

基本的に、スレッド間で制御を渡し、スレッドが動作するかどうかを決定します。そうでない場合は、単純に PulseAll (既存のすべての待機中のスレッドを準備完了キューに移動) し、Wait to (パルスされてから) 再びロックを取得します。ある時点で、いつ ThreadC を使用できるかが決まります。

public class ThreadsGettinCrazy {
  static readonly _sync = new object();
  static bool _threadCReady = false;

  public void ThreadA() {
    while (true) {
      lock(_sync) {
        while(/* my condition not met */) {
          Monitor.PulseAll(_sync);
          Monitor.Wait(_sync);
        }
        // do work, possibly set _threadCReady = true
        Monitor.PulseAll(_sync);
      }
      if (/* i'm done */) break;
    }
  }

  public void ThreadB() {
    while (true) {
      lock(_sync) {
        while(/* my condition not met */) {
          Monitor.PulseAll(_sync);
          Monitor.Wait(_sync);
        }
        // do work, possibly set _threadCReady = true
        Monitor.PulseAll(_sync);
      }
      if (/* i'm done */) break;
    }
  }

  public void ThreadC() {
    lock(_sync) {
      while (!_threadCReady) {
        Monitor.PulseAll(_sync);
        Monitor.Wait(_sync);
      }
      // do work
    }
  }
}
于 2010-03-06T07:08:23.120 に答える