ManualResetEvent与Thread.Sleep

Mat*_*ley 12 c# multithreading sleep manualresetevent

我实现了以下后台处理线程,其中JobsQueue<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.
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这产生正在进入作业时之间的noticable延迟,当他们实际上开始运行(作业批次在一旦进入,而每个工作只是[比较]小)的延迟是不是一个大问题,但我开始思考这个问题,并做了以下改变:

static ManualResetEvent _workerWait = new ManualResetEvent(false);
// ...
    if (job == null)
    {
        lock (_workerWait)
        {
            _workerWait.Reset();
        }
        _workerWait.WaitOne();
    }
Run Code Online (Sandbox Code Playgroud)

线程添加作业现在锁定_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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

nos*_*nos 16

事件是由OS/Kernel提供的内核原语,它是专门为这类事物设计的.内核提供了一个边界,您可以在该边界上保证对同步很重要的原子操作(某些原子性也可以在用户空间中通过硬件支持完成).

简而言之,当一个线程等待一个事件时,它会被置于该事件的等待列表中并被标记为不可运行.当事件发出信号时,内核唤醒等待列表中的那些并将它们标记为可运行并且它们可以继续运行.当事件被发出信号时,线程可以立即醒来,而不是长时间睡眠并且不时地重新检查状态,这自然是一个巨大的好处.

甚至一毫秒真的很长,你可以在那段时间处理成千上万的事件.此外,时间分辨率通常为10毫秒,因此睡眠时间不到10毫秒通常只会导致10毫秒的睡眠.通过事件,可以立即唤醒并安排线程

  • 更新的信息:10ms最小分辨率是XP和更早的事情,因为操作系统使用10ms的静态增量进行调度.我认为Vista,我知道Win7确实如此,使用动态的"无滴答"时间片.使用Win7,我可以启动一个高分辨率计时器,发出一个睡眠(1),时间非常接近1毫秒,有时小于. (2认同)

Ric*_*ard 10

第一次锁定_workerWait是没有意义的,Event是一个系统(内核)对象,设计用于线程之间的信令(并在Win32 API中大量用于异步操作).因此,多线程在没有额外同步的情况下设置或重置它是非常安全的.

至于你的主要问题,需要看到将事情放在队列中的逻辑,以及关于每项工作完成了多少工作的一些信息(工作者线程花费更多时间处理工作或等待工作).

可能最好的解决方案是使用对象实例来锁定并使用Monitor.PulseMonitor.Wait作为条件变量.

编辑:有了要排队的代码的视图,似乎答案#1116297说得对:1ms的延迟太长了,等待,因为许多工作项将非常快速地处理.

具有唤醒工作线程的机制的方法是正确的(因为没有具有阻塞出列操作的.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();
  }
}
Run Code Online (Sandbox Code Playgroud)

Monitor.Wait将释放对参数的锁定,等待Pulse()或被PulseAll()锁定,然后重新进入锁定并返回.需要重新检查等待条件,因为某些其他线程可能已从队列中读取该项.