概述
主要設(shè)計(jì)思想是通過一個(gè)共享隊(duì)列,,多個(gè)輸入端能同時(shí)非阻塞式的向隊(duì)列中增加記錄信息,輸出端能自動及時(shí)的把隊(duì)列中的記錄信息輸出到控制臺或是保存到 文件及數(shù)據(jù)庫中,。多個(gè)輸入端互相隔離,,采用多線程實(shí)現(xiàn),但考慮到緩存日志信息的是一個(gè)共享隊(duì)列,,自然涉及到線程間的同步問題,。本文的實(shí)現(xiàn)模式是采用操作系 統(tǒng)中很經(jīng)典的生產(chǎn)者/消費(fèi)者模式。線程間的同步是通過事件信號,,同時(shí)對共享隊(duì)列的修改進(jìn)行加鎖保護(hù),,避免多個(gè)線程同時(shí)修改隊(duì)列。
日志記錄類實(shí)現(xiàn)
整個(gè)實(shí)現(xiàn)除了主要的日志記錄類,,還要定義同步事件類封裝用于線程間同步的事件對象,,定義日志信息類用于生成日志信息能存于共享隊(duì)列中。
1. 同步事件類 SyncEvents
該類的定義與使用參照《如何:對制造者線程和使用者線程進(jìn)行同步》
public class SyncEvents { private EventWaitHandle _newItemEvent; //添加新項(xiàng) private EventWaitHandle _exitThreadEvent; //退出線程 private WaitHandle[] _eventArray; public SyncEvents() { _newItemEvent = new AutoResetEvent(false); _exitThreadEvent = new ManualResetEvent(false); _eventArray = new WaitHandle[2]; _eventArray[0] = _newItemEvent; _eventArray[1] = _exitThreadEvent; } public EventWaitHandle ExitThreadEvent { get { return _exitThreadEvent; } } public EventWaitHandle NewItemEvent { get { return _newItemEvent; } } public WaitHandle[] EventArray { get { return _eventArray; } } }
對新記錄的添加使用 AutoResetEvent 類,,輸出端線程在響應(yīng)此事件后,,此事件能自動重置。將 ManualResetEvent 類用于通知線程退出,,該事件被設(shè)置后無論是向共享隊(duì)列中添加日志記錄的輸入端線程還是從共享隊(duì)列中取日志記錄的輸出端線程都能響應(yīng)此事件,,從而正常退出。
2. 日志信息類
共享隊(duì)列中存放的就是日志信息類的實(shí)例對象,,可以根據(jù)實(shí)際需要對此類中的屬性進(jìn)行增加與修改,,這并不影響下面將要介紹的日志記錄類正常使用,。
public class LogInfo { private int _ID; public int ID { get { return _ID; } set { _ID = value; } } private string _CreateTime; public string CreateTime { get { return _CreateTime; } set { _CreateTime = value; } } private string _Content; public string Content { get { return _Content; } set { _Content = value; } } }
3. 日志記錄類
類中屬性與構(gòu)造函數(shù)
public class Logger { private static Logger _logger; private static object _lock = new object(); private static Thread _thread; //日志隊(duì)列 private Queue<LogInfo> _queue; private SyncEvents _syncEvents; private Logger() { _queue = new Queue<LogInfo>(); _syncEvents = new SyncEvents(); } //獲取日志記錄類實(shí)例 public static Logger GetLogger() { if (_logger == null) { //加鎖,防止多線程運(yùn)行時(shí),,重復(fù)創(chuàng)建,。 lock (_lock) { if (_logger == null) { _logger = new Logger(); } } } return _logger; } }
為了保證共享隊(duì)列唯一,此類實(shí)現(xiàn)采用了單例模式,,實(shí)現(xiàn)方式是通過定義一個(gè)靜態(tài)的 自身logger變量,,私有化默認(rèn)的構(gòu)造函數(shù),提供一個(gè)得到Logger實(shí)例的GetLogger方法,。這樣不能通過new直接創(chuàng)建Logger實(shí)例,,只 能通過GetLogger方法獲得,在該方法中就可以通過判斷是否已創(chuàng)建了Logger實(shí)例,,如果已創(chuàng)建則返回已有的,,從而保證Logger實(shí)例的唯一。
添加日志方法
private void AddLog(Object obj) { LogInfo log = obj as LogInfo; if (!_syncEvents.ExitThreadEvent.WaitOne(0, false)) { lock (((ICollection)_queue).SyncRoot) { _queue.Enqueue(log); _syncEvents.NewItemEvent.Set(); Console.WriteLine("Input thread: add {0} items", log.ID); } } } /// <summary> /// 添加日志 /// </summary> /// <param name="log"></param> public void Add(LogInfo log) { Thread t = new Thread(AddLog); t.Start(log); }
首先檢查“退出線程”事件,,因?yàn)?WaitOne 使用的第一個(gè)參數(shù)為零,,該方法會立即返回,所以檢查該事件的狀態(tài)不會阻止當(dāng)前線程,。接著往共享隊(duì)列中添加日志記錄并設(shè)置“添加新項(xiàng)”事件,,此事件設(shè)置后會 讓因共享隊(duì)列為空而一直在等待的輸出線程繼續(xù)運(yùn)行,處理共享隊(duì)列中的新日志記錄,。 日志添加通過調(diào)用Add方法,,啟動一個(gè)新線程運(yùn)行AddLog方法向共享隊(duì)列中添加新日志。
日志輸出方法
/// <summary> /// 日志保存 /// </summary> private void Save() { int flag = 0; while (flag >=0 ) { if (_queue.Count == 0) { flag = WaitHandle.WaitAny(_syncEvents.EventArray); if (flag == 1) { flag = -1; } } lock (((ICollection)_queue).SyncRoot) { if (_queue.Count > 0) { LogInfo log = _queue.Dequeue(); Console.WriteLine("Output Thread: process {0} items", log.ID); } } } } public void Run() { _thread = new Thread(Save); _thread.Start(); }
輸出線程主要運(yùn)行的就是日志保存方法,,通過while循環(huán)逐個(gè)處理共享隊(duì)列中的 日志記錄。如果隊(duì)列為空,,則線程暫停進(jìn)入等待狀態(tài),,等待“添加新項(xiàng)”事件或“退出線程”事件,兩個(gè)事件只要有一個(gè)被設(shè)置則線程繼續(xù)運(yùn)行,,如果是“退出線 程”事件,,則設(shè)置flag為-1,退出循環(huán)線程結(jié)束,,因?yàn)橹挥性陉?duì)列為空時(shí)才等待“退出線程”事件,,這樣保證線程退出前隊(duì)列中的所有的日志記錄都被處理。 在程序開始處就運(yùn)行Run方法,,會啟動一個(gè)新線程運(yùn)行Save方法,,這樣只要一添加日志就能自動的被處理。
線程結(jié)束方法
public void Stop() { _syncEvents.ExitThreadEvent.Set(); }
通過設(shè)置“退出線程”事件,,讓正在運(yùn)行的輸入線程和輸出線程都自動結(jié)束運(yùn)行,。
4. 使用示例
class Program { static void Main(string[] args) { Logger logger = Logger.GetLogger(); logger.Run(); for (int i = 0; i < 100; i++) { LogInfo log = new LogInfo(); log.ID = i; logger.Add(log); if (i == 50) { logger.Stop(); } } Console.ReadLine(); } }
這只是個(gè)人學(xué)習(xí)多線程相關(guān)知識而簡單實(shí)現(xiàn)的日志類,,功能簡單也未做任何優(yōu)化。如果需要在實(shí)際項(xiàng)目中使用日志記錄功能,,推薦開源的Nlog