我从C#代码示例here开始。我试图调整它有几个原因:1)在我的场景中,所有任务将在消费者开始之前预先放入队列中; 2)我想将工作者抽象为一个单独的类而不是Thread
类中的原始WorkerQueue
成员。
我的队列似乎并没有自行处理,它只是挂起,当我在Visual Studio中中断时,它会卡在_th.Join()
#1的WorkerThread
行上。另外,有更好的方法来组织这个吗?有关公开WaitOne()
和Join()
方法的内容似乎有误,但我想不出让WorkerThread
与队列进行交互的合适方式。
另外,如果我在q.Start(#)
块的顶部调用using
,则每次启动时只会有一些线程(例如,线程1,2和8处理每个任务)。为什么是这样?这是某种竞争条件,还是我做错了什么?
using System;
using System.Collections.Generic;
using System.Text;
using System.Messaging;
using System.Threading;
using System.Linq;
namespace QueueTest
{
class Program
{
static void Main(string[] args)
{
using (WorkQueue q = new WorkQueue())
{
q.Finished += new Action(delegate { Console.WriteLine("All jobs finished"); });
Random r = new Random();
foreach (int i in Enumerable.Range(1, 10))
q.Enqueue(r.Next(100, 500));
Console.WriteLine("All jobs queued");
q.Start(8);
}
}
}
class WorkQueue : IDisposable
{
private Queue<int> _jobs = new Queue<int>();
private int _job_count;
private EventWaitHandle _wh = new AutoResetEvent(false);
private object _lock = new object();
private List<WorkerThread> _th;
public event Action Finished;
public WorkQueue()
{
}
public void Start(int num_threads)
{
_job_count = _jobs.Count;
_th = new List<WorkerThread>(num_threads);
foreach (int i in Enumerable.Range(1, num_threads))
{
_th.Add(new WorkerThread(i, this));
_th[_th.Count - 1].JobFinished += new Action<int>(WorkQueue_JobFinished);
}
}
void WorkQueue_JobFinished(int obj)
{
lock (_lock)
{
_job_count--;
if (_job_count == 0 && Finished != null)
Finished();
}
}
public void Enqueue(int job)
{
lock (_lock)
_jobs.Enqueue(job);
_wh.Set();
}
public void Dispose()
{
Enqueue(Int32.MinValue);
_th.ForEach(th => th.Join());
_wh.Close();
}
public int GetNextJob()
{
lock (_lock)
{
if (_jobs.Count > 0)
return _jobs.Dequeue();
else
return Int32.MinValue;
}
}
public void WaitOne()
{
_wh.WaitOne();
}
}
class WorkerThread
{
private Thread _th;
private WorkQueue _q;
private int _i;
public event Action<int> JobFinished;
public WorkerThread(int i, WorkQueue q)
{
_i = i;
_q = q;
_th = new Thread(DoWork);
_th.Start();
}
public void Join()
{
_th.Join();
}
private void DoWork()
{
while (true)
{
int job = _q.GetNextJob();
if (job != Int32.MinValue)
{
Console.WriteLine("Thread {0} Got job {1}", _i, job);
Thread.Sleep(job * 10); // in reality would to actual work here
if (JobFinished != null)
JobFinished(job);
}
else
{
Console.WriteLine("Thread {0} no job available", _i);
_q.WaitOne();
}
}
}
}
}
答案 0 :(得分:5)
工作线程在DoWork()中的_q.WaitOne()调用上都是阻塞的。调用线程的Join()方法将死锁,线程永远不会退出。您需要添加一个机制来向工作线程发出信号以退出。在worker中使用WaitAny测试的ManualResetEvent将完成工作。
一个调试提示:熟悉Debug + Windows + Threads窗口。它允许您在线程之间切换并查看其调用堆栈。你很快就会发现这个问题。
答案 1 :(得分:1)
您在WaitOne()
结尾处执行了DoWork
,但是在线程开始运行后您从未设置过AutoResetEvent
。
请注意,“{1}}
WaitOne
将返回未设置状态
答案 2 :(得分:1)
您的DoWork方法中的循环永远不会完成。这将导致线程总是忙,这个thread.Join()将永远阻塞,等待它完成。
你有一个WaitOne,但我不认为这是必要的,除非有理由你希望你的线程池在你的工作完成后留下来:
private void DoWork()
{
bool done = false;
while (!done)
{
int job = _q.GetNextJob();
if (job != Int32.MinValue)
{
Console.WriteLine("Thread {0} Got job {1}", _i, job);
Thread.Sleep(job * 10); // in reality would to actual work here
if (JobFinished != null)
JobFinished(job);
}
else
{
Console.WriteLine("Thread {0} no job available", _i);
done = true;
}
}
}
如果您希望线程保持不变,那么在调用WorkQueue.Start时您不必重新分配更多线程,您必须使用AutoResetEvent做更精细的事情。
答案 3 :(得分:1)
您的主要问题是其他答案中描述的确定性死锁。
但是,处理它的正确方法不是解决死锁,而是完全消除事件。
生产者 - 消费者模型的整体思想是客户端同时对队列和排队元素进行排队,这就是需要同步机制的原因。如果你事先将所有元素排队,然后只是同时出列,你只需要对出队进行锁定,因为“事件”用于让“消费者”等待新元素入队;在您的情况下不会发生这种情况(根据您的描述)。
此外,“单一责任”设计原则建议将线程代码与“阻塞队列”代码分开。使“阻塞队列”成为它自己的一个类,然后在你的线程管理类中使用它。