13

次のバックグラウンド処理スレッドを実装しました。ここJobsで、はQueue<T>

static void WorkThread()
{
    while (working)
    {
        var job;

        lock (Jobs)
        {
            if (Jobs.Count > 0)
                job = Jobs.Dequeue();
        }

        if (job == null)
        {
            Thread.Sleep(1);
        }
        else
        {
            // [snip]: Process job.
        }
    }
}

これにより、ジョブが入力されてから実際に実行が開始されるまでの間に顕著な遅延が発生しました(ジョブのバッチは一度に入力され、各ジョブは[比較的]小さいだけです)。遅延はそれほど大きくありませんでした。しかし、私は問題について考えるようになり、次の変更を加えました。

static ManualResetEvent _workerWait = new ManualResetEvent(false);
// ...
    if (job == null)
    {
        lock (_workerWait)
        {
            _workerWait.Reset();
        }
        _workerWait.WaitOne();
    }

ジョブを追加するスレッドがロックされ、ジョブの追加が完了する_workerWaitと呼び出さ_workerWait.Set()れるようになりました。このソリューションは(一見)即座にジョブの処理を開始し、遅延は完全になくなります。

私の質問は、一部は「なぜこれが起こるのか」でThread.Sleep(int)あり、指定したよりも長く非常によく眠ることができると認められており、一部は「このレベルのパフォーマンスをどのようにManualResetEvent達成するのですか?」です。

編集:誰かがアイテムをキューに入れる機能について尋ねたので、これは現在の完全なシステムと一緒にあります。

public void RunTriggers(string data)
{
    lock (this.SyncRoot)
    {
        this.Triggers.Sort((a, b) => { return a.Priority - b.Priority; });

        foreach (Trigger trigger in this.Triggers)
        {
            lock (Jobs)
            {
                Jobs.Enqueue(new TriggerData(this, trigger, data));
                _workerWait.Set();
            }
        }
    }
}

static private ManualResetEvent _workerWait = new ManualResetEvent(false);
static void WorkThread()
{
    while (working)
    {
        TriggerData job = null;

        lock (Jobs)
        {
            if (Jobs.Count > 0)
                job = Jobs.Dequeue();

            if (job == null)
            {
                _workerWait.Reset();
            }
        }

        if (job == null)
            _workerWait.WaitOne();
        else
        {
            try
            {
                foreach (Match m in job.Trigger.Regex.Matches(job.Data))
                    job.Trigger.Value.Action(job.World, m);
            }
            catch (Exception ex)
            {
                job.World.SendLineToClient("\r\n\x1B[32m -- {0} in trigger ({1}): {2}\x1B[m",
                    ex.GetType().ToString(), job.Trigger.Name, ex.Message);
            }
        }
    }
}
4

2 に答える 2

18

イベントは、この種のもののためだけに設計されたOS/カーネルによって提供されるカーネルプリミティブです。カーネルは、同期に重要なアトミック操作を保証できる境界を提供します(一部のアトミック性は、ハードウェアサポートを使用してユーザースペースでも実行できます)。

つまり、スレッドがイベントを待機すると、そのイベントの待機リストに入れられ、実行不可としてマークされます。イベントが通知されると、カーネルは待機リストにあるものをウェイクアップし、実行可能としてマークし、実行を継続できます。長い間スリープして時々状態を再確認するのではなく、イベントが通知されるとすぐにスレッドがウェイクアップできることは、当然のことながら大きな利点です。

1ミリ秒でも非常に長い時間であり、その間に何千ものイベントを処理できた可能性があります。また、時間分解能は従来10ミリ秒であるため、通常10ミリ秒未満のスリープでは、とにかく10ミリ秒のスリープになります。イベントを使用すると、スレッドをウェイクアップしてすぐにスケジュールできます

于 2009-07-12T15:58:48.530 に答える
10

最初のロックオン_workerWaitは無意味です。イベントは、スレッド間のシグナリング用に設計されたシステム(カーネル)オブジェクトです(非同期操作用のWin32 APIで頻繁に使用されます)。したがって、複数のスレッドが追加の同期なしで設定またはリセットすることは非常に安全です。

主な質問については、キューに配置するためのロジックと、各ジョブで実行される作業量に関する情報も確認する必要があります(ワーカースレッドは、作業の処理または作業の待機により多くの時間を費やしています)。

おそらく最善の解決策は、オブジェクトインスタンスを使用してロックオンしMonitor.PulseMonitor.Wait条件変数として使用することです。

編集:エンキューするコードを見ると、回答#1116297は正しいようです。多くの作業項目の処理が非常に速いことを考えると、1ミリ秒の遅延は待つには長すぎます。

ワーカースレッドをウェイクアップするメカニズムを持つというアプローチは正しいです(ブロッキングデキュー操作を伴う.NET同時キューがないため)。ただし、イベントを使用するよりも、条件変数の方が少し効率的です(競合しない場合のように、カーネル遷移は必要ありません)。

object sync = new Object();
var queue = new Queue<TriggerData>();

public void EnqueueTriggers(IEnumerable<TriggerData> triggers) {
  lock (sync) {
    foreach (var t in triggers) {
      queue.Enqueue(t);
    }
    Monitor.Pulse(sync);  // Use PulseAll if there are multiple worker threads
  }
}

void WorkerThread() {
  while (!exit) {
    TriggerData job = DequeueTrigger();
    // Do work
  }
}

private TriggerData DequeueTrigger() {
  lock (sync) {
    if (queue.Count > 0) {
      return queue.Dequeue();
    }
    while (queue.Count == 0) {
      Monitor.Wait(sync);
    }
    return queue.Dequeue();
  }
}

Pulse()Monitor.Waitは、パラメーターのロックを解除し、またはロックに対して呼び出されるまで待機してからPulseAll()、ロックに再度入り、戻ります。他のスレッドがキューからアイテムを読み取った可能性があるため、待機条件を再確認する必要があります。

于 2009-07-12T15:44:36.793 に答える