我实现了以下后台处理线程,其中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);
}
}
}
}发布于 2009-07-12 15:58:48
事件是由OS/Kernel提供的内核原语,该内核就是为这类事情而设计的。内核提供了一个边界,您可以在此边界上保证原子操作,这对于同步非常重要(在硬件支持下,也可以在用户空间中实现一些原子性)。
简而言之,当一个线程等待一个事件时,它会被放置在该事件的等待列表中,并被标记为不可运行。当事件发出信号时,内核会唤醒等待列表中的事件,并将它们标记为runnable,它们可以继续运行。这当然是一个巨大的好处,一个线程可以立即醒来时,事件是信号,与睡眠了很长时间,并不时地重新检查情况。
即使是毫秒也是一段很长的时间,你可以在这段时间内处理成千上万的事件。另外,传统的时间分辨率是10毫秒,所以睡眠少于10毫秒通常只会导致10毫秒的睡眠。对于事件,线程可以被唤醒并立即调度。
发布于 2009-07-12 15:44:36
第一次锁定_workerWait是没有意义的,事件是一个系统(内核)对象,用于在线程之间发送信令(并在异步操作的Win32 API中大量使用)。因此,对于多个线程来说,在没有额外同步的情况下设置或重置它是非常安全的。
至于您的主要问题,也需要查看将事情放在队列中的逻辑,以及关于每个任务完成了多少工作的一些信息(是工作线程花费更多的时间处理工作还是等待工作)。
最好的解决方案可能是使用对象实例锁定Monitor.Pulse和Monitor.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();
}
}Monitor.Wait将释放参数上的锁,等待对锁调用Pulse()或PulseAll(),然后重新输入锁并返回。需要重新检查等待条件,因为其他线程可能已从队列中读取该项。
https://stackoverflow.com/questions/1116249
复制相似问题