專案需將兩種異構存儲設備的海量數據進行格式轉換並遷移。單線程順序讀寫耗時過長,必須引入多線程並行。核心難點在於:
+----------------+ +----------------------+ +------------------+
| 讀取線程 (1) | ---> | SynchronizedQueue | ---> | 寫入線程 (N) |
| ReadFromExt() | | ReaderWriterLockSlim| | SaveToLocal() |
+----------------+ +----------------------+ +------------------+
Queue<T> 封裝 ReaderWriterLockSlim,提供讀寫分離鎖,支援超時嘗試寫入,防止生產者無限阻塞。namespace Common {
public interface IReaderWriter<T> {
T Read(int argument);
void Write(int argument, T instance);
void Delete(int argument);
void Clear();
}
}
public class QueueReaderWriter<T> : IReaderWriter<T> {
private readonly Queue<T> _queues;
public QueueReaderWriter() : this(0) { }
public QueueReaderWriter(int capacity) { _queues = new Queue<T>(capacity); }
public T Read(int argument) => _queues.FirstOrDefault();
public void Write(int argument, T instance) => _queues.Enqueue(instance);
public void Delete(int argument) => _queues.Dequeue();
public void Clear() { _queues.Clear(); _queues.TrimExcess(); }
}
public class SynchronizedWriteData<T> : IDisposable {
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private readonly IReaderWriter<T> _inner;
public SynchronizedWriteData(IReaderWriter<T> inner) { _inner = inner; }
// 讀取(共享鎖)
public T Read() {
_lock.EnterReadLock();
try { return _inner.Read(0); }
finally { _lock.ExitReadLock(); }
}
// 寫入(獨佔鎖)
public void Add(T instance) {
_lock.EnterWriteLock();
try { _inner.Write(0, instance); }
finally { _lock.ExitWriteLock(); }
}
// 帶超時寫入,防止生產者無限等待
public bool AddWithTimeout(T instance, int timeoutMs) {
if (_lock.TryEnterWriteLock(timeoutMs)) {
try { _inner.Write(0, instance); return true; }
finally { _lock.ExitWriteLock(); }
}
return false; // 超時可選擇丟棄、重試或記錄日誌
}
public void Delete() {
_lock.EnterWriteLock();
try { _inner.Delete(0); }
finally { _lock.ExitWriteLock(); }
}
public void Dispose() {
_lock.EnterWriteLock();
try { _inner.Clear(); }
finally { _lock.ExitWriteLock(); _lock.Dispose(); }
}
}
public abstract class ExternalDataAdapter<T> : IDisposable {
protected abstract string ConnectString { get; }
protected abstract void Initialize();
public abstract void Transmit();
protected abstract void ReadFromExternalDevice();
protected abstract void SaveToInternalDevice();
public abstract void Dispose();
}
public abstract class MultiThreadAdapter<T> : ExternalDataAdapter<T> {
protected SynchronizedWriteData<T> _data;
protected Thread _readThread;
public sealed override void Transmit() {
_readThread = new Thread(ReadFromExternalDevice) { IsBackground = true };
_readThread.Start();
Thread.Sleep(10000); // 給生產者預填充時間,避免消費者空跑
while (_readThread.IsAlive) {
SaveToInternalDevice(); // 消費者循環取數據
}
_readThread.Join(); // 等待生產者結束
}
protected abstract override void ReadFromExternalDevice();
protected abstract override void SaveToInternalDevice();
public override void Dispose() { _data?.Dispose(); }
}
| 決策點 | 方案 | 理由 |
|---|---|---|
| 鎖選擇 | ReaderWriterLockSlim | 讀多寫少場景,共享鎖允許多消費者並發讀取,獨佔鎖保證生產者寫入原子性,較 lock/Monitor 吞吐更高 |
| 隊列類型 | Queue<T> | 先進先出符合數據流順序,記憶體連續,Enqueue/Dequeue 為 O(1) |
| 唯一性校驗 | 下游資料庫約束或寫入時查詢 | 隊列消費即丟棄,無法回溯,避免在隊列層維護 HashSet 造成額外記憶體與鎖開銷 |
| 生產者預填充 | Thread.Sleep(10000) | 啟動消費者前讓隊列積累一定數據,減少初期空讀 CPU 浪費 |
| 超時寫入 | AddWithTimeout | 外部設備讀取受阻時避免生產者線程永久阻塞,支援熔斷或降級策略 |
SynchronizedWriteData 中加入最大容量限制,達到上限時 AddWithTimeout 返回 false,生產者可選擇暫停讀取或丟棄非關鍵數據。SaveToInternalDevice 可一次 Dequeue 多筆(如 while(count-- > 0 && queue.TryDequeue(out var item))),配合資料庫批次插入(SqlBulkCopy、INSERT ... VALUES (...),(...))大幅提升寫入吞吐。基於 ReaderWriterLockSlim 封裝 Queue<T> 實現的生產消費模式,以極低的鎖爭用實現了高吞吐的異構數據遷移。抽象出 IReaderWriter<T>、ExternalDataAdapter<T> 等介面,使具體設備驅動開發聚焦於協議解析與格式轉換,複用同步調度邏輯。實踐中配合批次寫入、背壓控制、監控告警,可平穩支撐 TB 級數據遷移任務。