背景
最近在做一個圖像處理的 WPF 項目,底層使用 Halcon 的 HObject
來存放圖像。為了減少頻繁創建和釋放對象帶來的開銷,我實現了一個對象池,用來存放 HObject
,方便后續流程復用。
最初的實現用的是 .NET 自帶的 Queue<T>
:
private readonly Queue<T> objects = new Queue<T>();
配合 lock
實現線程安全,在 GetObject
時取出一個對象,ReturnObject
時放回隊列。
一切看似順利,但隨著功能擴展,我遇到了兩個問題:
-
多線程調用時有潛在的性能瓶頸
lock
保護了隊列,但在高并發下會阻塞其他線程。 -
對象生命周期混亂
在顯示后立即歸還對象時,有時會出現CurImg
已經被釋放的情況,導致后續流程無法使用。
于是,我開始思考:是不是可以用 ConcurrentQueue
來替代 Queue
+ lock
?
Queue vs ConcurrentQueue 對比
特性 | Queue | ConcurrentQueue |
---|---|---|
線程安全 | ? 需要手動加 lock | ? 內置線程安全 |
性能 | 在單線程或低并發下更快 | 在多線程下更優,避免鎖競爭 |
操作方式 | Enqueue / Dequeue | Enqueue / TryDequeue |
Count 屬性 | 精確(單線程) | 近似值(多線程下可能不是實時) |
適用場景 | 單線程隊列或少量鎖保護的情況 | 高并發讀寫隊列、生產者-消費者模式 |
改造過程
我將原先的 Queue<T>
換成了 ConcurrentQueue<T>
,并去掉了多余的 lock
。
原始版本(Queue + lock)
public T GetObject()
{lock (objects){if (objects.Count > 0)return objects.Dequeue();elsereturn new T();}
}
改造版本(ConcurrentQueue)
public T GetObject()
{if (objects.TryDequeue(out var obj)){return obj;}return new T();
}
改造后的好處
-
線程安全更自然
ConcurrentQueue
內部使用了無鎖算法,減少了阻塞等待的情況。 -
代碼更簡潔
不再需要手動加lock
,也避免了忘記加鎖導致的潛在 bug。 -
性能在多線程下更優
多個線程可以同時安全地讀寫隊列。
需要注意的坑
改造后,我也踩了幾個坑:
-
不要先判斷 Count 再操作
在多線程下,Count
只是一個快照值。
如果寫成:if (objects.Count > 0)objects.TryDequeue(out var obj);
就可能在判斷到取出之間,隊列已經被別的線程清空,導致邏輯不一致。
? 正確寫法:直接用
TryDequeue
判斷并取值。 -
Count 在容量控制上的延遲
當多個線程同時ReturnObject
,可能短暫超過_maxPoolSize
。
我的處理方式是用while
循環清理多余對象:while (objects.Count > _maxPoolSize && objects.TryDequeue(out var old)) {if (old is IDisposable disposable)disposable.Dispose(); }
雖然會有一點“超限再回落”,但影響不大。
關于 HObject 的思考
在改造過程中,我發現 HObject
這種一次性資源(Dispose
后不可復用)其實不太適合放到傳統意義的“對象池”里。
但是我為了自動化釋放管理,同時不愿意立馬釋放當前圖像,所以這么做了。
總結
從這次改造中,我有幾點心得:
- 如果是高并發的隊列操作,
ConcurrentQueue
是更優解,省去了手動加鎖的麻煩。 - 多線程下不要依賴
Count
做邏輯判斷,直接用TryDequeue
更安全。 - 改造代碼時,不要只關注語法,還要考慮資源生命周期,否則會出現“對象提前被釋放”的問題。
💡 一句話總結:
在多線程隊列管理上,
ConcurrentQueue
是比Queue+lock
更簡潔的選擇,但資源的生命周期管理才是對象池真正的難點。