我正在.net项目3.5上实施Producer / Consumer aproach
只有一个生产者和一个消费者各自在自己的线程上运行
CheckOrderToProcess
方法检查符合特定条件的表并将它们添加到列表中(生产者)
bgOrdenes_DoWork
方法获取列表中的每个项目并执行一些逻辑(消费者)
我想避免锁定整个列表lstOrderToProcessto
以提高性能,我尝试使用ConcurrentQueue
但由于将要使用的限制我无法升级到.net 4.0在无法升级到4.0的项目中
如何更改此实现以提高性能?我不必是一个列表,只要它可以在线程之间共享,我可以在最后添加并获取第一个元素(队列)
public class DatabaseCache : ICacheDB
{
private static List<NotificacionOrdenes> lstOrderToProcess;
private static Object lockOrders;
private static BackgroundWorker bgOrdenes;
private static bool pendingOrders = false;
private static Timer timerCheck;
public DatabaseCache(string eventSourceName, bool monitorearOrderNotifications)
{
bgOrdenes = new BackgroundWorker();
bgOrdenes.DoWork += new DoWorkEventHandler(bgOrdenes_DoWork);
lstOrderToProcess = new List<NotificacionOrdenes>();
lockOrders = new Object();
CheckOrderToProcess();
if (!bgOrdenes.IsBusy)
{
bgOrdenes.RunWorkerAsync();
}
//execute CheckOrderToProcess periodically
timerCheck = new Timer(2000);
timerCheck.Elapsed += new ElapsedEventHandler(timerCheck_Elapsed);
timerCheck.Enabled = true;
timerCheck.AutoReset = true;
}
void timerCheck_Elapsed(object sender, ElapsedEventArgs e)
{
CheckOrderToProcess();
}
private void CheckOrderToProcess()
{
DataSet ds;
NotificacionOrdenes notif;
ds = Data.GetOrderNotifications_ToProcess();
//if there is new info to process
if ((ds != null) && (ds.Tables[0].Rows.Count != 0))
{
foreach (DataRow row in ds.Tables[0].Rows)
{
notif = new NotificacionOrdenes();
//fill NOTIF with info of each row
lock (lockOrders)
{
lstOrderToProcess.Add(notif);
}
}
pendingOrders = true;
}
}
void bgOrdenes_DoWork(object sender, DoWorkEventArgs e)
{
NotificacionOrdenes notif;
while (true)
{
if (pendingOrders)
{
lock (lockOrders)
{
notif = lstOrderToProcess[0];
lstOrderToProcess.RemoveAt(0);
//check to see if there is any pending order
pendingOrders = lstOrderToProcess.Any();
}
//Execute rest of the logic
}
}
}
}
答案 0 :(得分:1)
在此代码段中无法改变锁定的可能原因。然而,它确实遭受了相当令人讨厌的设计缺陷:
DoWork内部的等待循环是热等待循环。它燃烧100%核心,99.99999%的时间没有任何成就。这通常对您运行此代码的计算机非常不友好。它还会导致您的程序对添加的项目没有响应,即使您在尝试检测时也会烧掉大量的cpu周期。在你烧掉量子之后,操作系统的线程调度程序会让你进入狗屋一段时间。
pendingOrders 变量用作同步对象,但只是一个简单的 bool 变量。当你这样做时,很多事情都会出错。首先,当您运行代码的Release版本时,您的代码永远不会将变量设置为true,这是很有可能的。在32位代码中存在问题,必须将此类变量声明为 volatile 。它也是低效的,在线程可以观察到指定的值之前可能需要一段时间。
使用lstOrderToProcess.Any()是低效的。无论如何,只要删除索引0处的元素,无论如何都无法清空整个列表。
消费者在BackgroundWorker上运行。它使用线程池线程来实现worker。 TP线程通常不应运行超过半秒钟。但是你的线程会永远运行,严重阻碍了线程池调度程序的工作,以获得待执行的待处理tp线程请求。这会对整个应用的响应能力产生负面影响。
通过使用常规线程来运行消费者来获得成功。并为列表使用更好的设计,您需要阻止队列。你可以使用线程大师的代码获得在旧.NET版本上运行的版本,Joe Duffy的sample implementation解决了你的热等待循环问题。我会在这里重新发布,调整为不等消费者:
public class BlockingQueue<T> {
private Queue<Cell<T>> m_queue = new Queue<Cell<T>>();
public void Enqueue(T obj) {
Cell<T> c = new Cell<T>(obj);
lock (m_queue) {
m_queue.Enqueue(c);
Monitor.Pulse(m_queue);
}
}
public T Dequeue() {
Cell<T> c;
lock (m_queue) {
while (m_queue.Count == 0) Monitor.Wait(m_queue);
c = m_queue.Dequeue();
}
return c.m_obj;
}
}
class Cell<T> {
internal T m_obj;
internal Cell(T obj) {
m_obj = obj;
}
}
答案 1 :(得分:0)
如果我在你的情况下做生产者/消费者,那么我也会从List<>
开始。
但正如我现在看到的那样,这不是最好的情况,因为从列表中删除项目会导致索引被更改(这需要锁定整个列表)。
也许您可以使用数组代替?像这样:
NotificacionOrdenes[] todo = new NotificacionOrdenes[1000];
// producer
// find empty slot (null) in todo somehow
todo[empty_slot] = new NotificacionOrdenes();
...
// consumer
// find non-empty slot somehow
var notif = todo[non_empty_slot]
todo[non_empty_slot] = null;
..
正如您所看到的那样,不需要锁定(检查null并设置为null是安全的),但是当没有空槽,数组太小或消费者太慢时,您必须处理情况。