[英]How to improve performance on two thread accesing a collection of items concurrently
我正在.net項目3.5上實現Producer / Consumer方法
只有一個生產者和一個消費者在各自的線程上運行
CheckOrderToProcess
方法檢查符合某些條件的表並將其添加到列表中(生產者)
bgOrdenes_DoWork
方法獲取列表中的每個項目並執行一些邏輯(消費者)
我想避免鎖定整個列表lstOrderToProcessto
以提高性能,我嘗試使用ConcurrentQueue
但由於無法在無法升級到4.0的項目中使用而受到限制,因此我無法升級到.net 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
}
}
}
}
沒有可能的原因來更改此代碼段中的鎖定。 但是,它確實存在一些令人討厭的設計缺陷:
DoWork內部的等待循環是一個熱等待循環 。 它消耗100%的內核,99.99999%的時間不完成任何操作。 通常,這對運行此代碼的計算機非常不友好。 即使您在嘗試檢測大量CPU周期時也將導致程序對添加的項目無響應。 刻錄完量子后,操作系統的線程調度程序會將您保留在狗屋中一段時間。
待定變量用作同步對象,但僅僅是一個簡單的布爾變量。 當您這樣做時,很多事情都會出錯。 一方面,很有可能您的代碼在運行代碼的Release版本時從未看到變量設置為true。 在32位代碼中存在問題,此類變量必須聲明為volatile 。 這也是低效的,線程可能需要一段時間才能觀察到分配的值。
lstOrderToProcess.Any()的使用效率低下。 實際上,僅刪除索引0處的元素將毫無意義地清空整個列表。
使用者在BackgroundWorker上運行。 它使用線程池線程來實現工作程序。 TP線程通常不應運行超過半秒。 但是,您的線程將永遠運行,嚴重阻礙了線程池調度程序的工作,以使待執行的tp線程請求得到執行。 這會對整個應用程序的響應性產生負面影響。
通過使用常規線程運行使用者來取得成功。 為列表使用更好的設計,您需要Blocking Queue 。 通過使用線程管理大師提供的代碼,您可以獲得在舊.NET版本上運行的代碼,Joe Duffy的示例實現解決了您的熱點等待循環問題。 我將其重新發布在此處,進行了調整,使其不等待消費者:
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;
}
}
如果我要在您的情況下做生產者/消費者,那么我也將從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是安全的),但是如果數組太小或使用者太慢,則沒有空插槽時,您必須處理這種情況。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.