在高併發網路程式設計中,頻繁分配/釋放位元組陣列會造成嚴重的 GC 壓力與記憶體碎片。緩衝池模式透過預先分配固定大小的緩衝區並循環重用,顯著降低記憶體分配開銷。本文展示完整的 C# DataBuffer 與 BufferPool 實作,包含執行緒安全、IDisposable 模式、多池負載均衡等生產級特性。
public class DataBuffer : IDisposable
{
public byte[] Data; // 底層儲存陣列
private int mLength; // 緩衝區總容量
private int mPosition = 0; // 當前讀寫位置
internal int mCount = 0; // 目前有效資料長度
internal BufferPool Pool { get; set; } // 所屬池,用於 Dispose 時歸還
// 以現有陣列建立 (資料已存在)
public DataBuffer(byte[] data) {
Data = data;
mLength = data.Length;
mPosition = 0;
mCount = data.Length;
}
// 指定容量建立 (空緩衝區)
public DataBuffer(int length) {
mLength = length;
Data = new byte[length];
}
// 從外部陣列複製資料
public void From(Array source, int index, int count) {
Array.Copy(source, index, Data, 0, count);
mPosition = 0;
mCount = count;
}
// 寫入資料,返回實際寫入位元組數
public int Write(byte[] data) => Write(data, 0);
public int Write(byte[] data, int index) {
int available = mLength - mPosition;
int request = data.Length - index;
int count = Math.Min(available, request);
if (count > 0) {
Array.Copy(data, index, Data, mPosition, count);
mPosition += count;
mCount += count;
}
return count;
}
// 讀取資料,返回 ArraySegment 避免複製
public ArraySegment<byte> Read(int count) {
int end = count;
if (mPosition + count > mCount)
end = mCount - mPosition;
var result = new ArraySegment<byte>(Data, mPosition, end);
mPosition += end;
return result;
}
// 重設位置
public void Seek() => Seek(0);
public void Seek(int position) => mPosition = position;
// 取得有效資料區段
public ArraySegment<byte> GetSegment() => new ArraySegment<byte>(Data, 0, mCount);
// 歸還緩衝池
public void Dispose() {
if (Pool != null) {
mPosition = 0;
mCount = 0;
Pool.Push(this);
}
}
}
mPosition 支援流式讀寫、隨機存取Write 自動限制寫入量,防止緩衝區溢位using 區塊自動歸還池中,杜絕洩漏
public class BufferPool : IDisposable
{
private static List<BufferPool> mPools = new List<BufferPool>();
private static int mIndex = 0;
private readonly Queue<DataBuffer> mBuffers;
private readonly int mBufferLength;
private bool mDisposed = false;
// 建構函數:預先分配指定數量緩衝區
public BufferPool(int count, int bufferLength) {
mBufferLength = bufferLength;
mBuffers = new Queue<DataBuffer>(count);
for (int i = 0; i < count; i++)
mBuffers.Enqueue(CreateBuffer(bufferLength));
}
private DataBuffer CreateBuffer(int length) {
var item = new DataBuffer(length);
item.Pool = this;
return item;
}
// 取得緩衝區:池空時自動擴容
public DataBuffer Pop() {
lock (mBuffers) {
return mBuffers.Count > 0 ? mBuffers.Dequeue() : CreateBuffer(mBufferLength);
}
}
// 歸還緩衝區
public void Push(DataBuffer buffer) {
lock (mBuffers) {
mBuffers.Enqueue(buffer);
}
}
// 靜態工廠:建立多個池以分散鎖競爭
public static void Setup(int pools, int buffers) => Setup(pools, buffers, 2048);
public static void Setup(int pools, int buffers, int bufferLength) {
lock (mPools) {
for (int i = 0; i < pools; i++)
mPools.Add(new BufferPool(buffers, bufferLength));
}
}
// 取得池:循環分發實現負載均衡
public static BufferPool GetPool() {
lock (mPools) {
if (mIndex == mPools.Count) mIndex = 0;
return mPools[mIndex++];
}
}
// 清理所有池
public static void Clean() {
lock (mPools) {
foreach (var pool in mPools) pool.Dispose();
mPools.Clear();
}
}
// 釋放池資源
private void OnDispose() {
lock (mBuffers) {
while (mBuffers.Count > 0)
mBuffers.Dequeue().Pool = null; // 斷開關聯防止誤歸還
}
}
public void Dispose() {
lock (this) {
if (!mDisposed) {
OnDispose();
mDisposed = true;
}
}
}
}
| 問題 | 單池 | 多池 + 輪詢分發 |
|---|---|---|
| 鎖競爭 | 高併發下單一 Queue 鎖熱點 | 分散到多個獨立鎖 |
| 快取親和性 | 跨核心存取同一 Queue | 執行緒傾向固定池,提升快取命中 |
| 擴容衝突 | 同時擴容觸發大量分配 | 分散擴容壓力 |
// 應用啟動時初始化:4 個池,每池 100 個 4KB 緩衝區
BufferPool.Setup(4, 100, 4096);
// 網路接收迴圈
var pool = BufferPool.GetPool();
using (var buffer = pool.Pop()) {
int received = socket.Receive(buffer.Data, 0, buffer.Data.Length, SocketFlags.None);
if (received > 0) {
buffer.mCount = received;
ProcessData(buffer.GetSegment());
}
} // 自動歸還池中
// 發送端同樣受益
using (var buffer = pool.Pop()) {
buffer.From(messageBytes, 0, messageBytes.Length);
socket.Send(buffer.GetSegment());
}
ArrayPool<byte>.Shared (`.NET Core 2.1+`) 替代自建池ValueTask 與 Memory<byte> 實作零拷貝非同步管線緩衝池是高效能網路程式的基石組件。DataBuffer + BufferPool 的組合提供了:
ArraySegment<byte>)掌握此模式,是構建百萬併發伺服器、高頻交易系統、即時通訊網關的關鍵技能之一。