实时多线程系统的日志实现
實時多線程系統(tǒng)的日志實現(xiàn)
2008-03-21 09:19 黃明/戴穎 軟件世界 我要評論(0) 字號:T | T為了分析軟件系統(tǒng)在測試和運行期產(chǎn)生的故障,目前大多數(shù)軟件系統(tǒng)所廣泛使用的一種方法就是日志記錄。本文給出了利用循環(huán)緩沖區(qū)和單獨的日志讀寫線程實現(xiàn)實時系統(tǒng)的日志功能。
AD:
為了分析軟件系統(tǒng)在測試和運行期產(chǎn)生的故障,目前大多數(shù)軟件系統(tǒng)所廣泛使用的一種方法就是日志記錄。但系統(tǒng)在保存日志時需要大量的磁盤I/O操作,同時由于多線程并發(fā)互斥訪問文件系統(tǒng)可能造成的阻塞,會引起實時軟件系統(tǒng)的性能下降,嚴重時造成整個系統(tǒng)停止響應(yīng)。本文給出了利用循環(huán)緩沖區(qū)和單獨的日志讀寫線程實現(xiàn)實時系統(tǒng)的日志功能。
“如果有兩種方式可以編寫出沒有錯誤的程序,那么只有第三種方式是有效的。” —ALGOL語言之父Alan J. Perlis。
任何軟件系統(tǒng),都不可避免的存在設(shè)計上的BUG,發(fā)現(xiàn)BUG和解決BUG貫穿于軟件系統(tǒng)的整個生命期。當(dāng)軟件系統(tǒng)產(chǎn)生非預(yù)期的結(jié)果時,分析故障原因成為首要任務(wù)。對于非實時的單線程程序,可采取設(shè)置斷點、單步跟蹤等手段,能比較容易地確定故障點。然而對于實時的多線程軟件系統(tǒng)來說,這些方法不能滿足實時性要求,從而在調(diào)試過程中往往使系統(tǒng)變得不可用。因此日志記錄成為關(guān)鍵的計算機應(yīng)用系統(tǒng)的生存期中一件非常重要的活動。通過分析在系統(tǒng)出現(xiàn)故障時的日志,即可確定故障原因。在運行過程中,系統(tǒng)將不斷地產(chǎn)生跟蹤數(shù)據(jù),并將其寫入到磁盤上的文本文件中。通常的做法是在程序運行的關(guān)鍵點把系統(tǒng)的狀態(tài)信息寫入磁盤,然而這種做法的副作用就是,由于磁盤I/O操作所耗費的時間往往遠大于系統(tǒng)中實際工作線程的執(zhí)行時間,甚至一次磁盤操作消耗的時間比一個線程的整個生命期還要長,這就影響了線程的執(zhí)行速度,再考慮到線程間的同步運行,其他線程可能會等待該線程的執(zhí)行結(jié)果,這樣就會造成整個系統(tǒng)性能的下降,不能滿足實時系統(tǒng)的要求。同時由于可能不只一個線程在記錄運行日志,因此會有多個線程并發(fā)的訪問磁盤,造成線程間的長時間阻塞和等待,更大大影響了系統(tǒng)運行時的響應(yīng)速度,嚴重時可能造成系統(tǒng)停止響應(yīng)。這種情況在實時性要求高的系統(tǒng)中是不允許的。
筆者在最近為IPSWITCH系統(tǒng)開發(fā)的通信網(wǎng)關(guān)中,利用日志線程和循環(huán)緩沖區(qū)解決了上述問題。本文把所有用來實現(xiàn)系統(tǒng)自身功能的線程統(tǒng)稱為“工作線程”,用于讀寫磁盤記錄日志的線程稱為“日志線程”,一個可循環(huán)使用、被所有線程共享的固定大小的內(nèi)存區(qū)域稱為“循環(huán)緩沖區(qū)”。
在工作線程中,在需要記錄日志的地方把程序運行的狀態(tài)信息寫入循環(huán)緩沖區(qū),再由單獨的日志線程讀出并寫入磁盤保存。在工作線程把日志寫入緩沖區(qū)時占用的時間很少,基本不影響工作線程運行。在日志線程進行磁盤I/O操作時,不占用工作線程的運行時間,同時由于只有一個單獨的日志線程對單個日志文件進行讀寫,因此完全避免了由于并發(fā)操作同一文件而造成的線程間的阻塞。
以下是實現(xiàn)上述功能的偽代碼(根據(jù)面向?qū)ο笏枷朐O(shè)計成日志類):
| 日志類說明 Class ClassLogThread private: var Head//循環(huán)緩沖區(qū)寫入指針 Tail//循環(huán)緩沖區(qū)讀取指針 LogCount//緩沖區(qū)中待處理日志記數(shù)器 Buffer[1024]//循環(huán)緩沖區(qū)(大小可根據(jù)實際情況在這里是一個字符串?dāng)?shù)組,數(shù)組中每個元素代表一行日志) Lock//同步變量,用于多線程同時讀寫循環(huán)緩沖區(qū)并發(fā)控制 F//日志文件句柄 procedureExecute//線程函數(shù)體,實現(xiàn)日志線程功能 public: procedureADD(狀態(tài)信息)//工作線程調(diào)用,把狀態(tài)信息寫入緩沖區(qū) 日志類實現(xiàn) 構(gòu)造函數(shù) create begin getmem(Buffer)//申請緩沖區(qū) Head <- 0 //初始化指針 Tail <- 0 LogCount <- 0//初始化待處理日志記數(shù)器 F <- Open(Filename)//創(chuàng)建或打開日志文件 Createthread(logthread,execute)//創(chuàng)建日志線程并指定線程函數(shù) end 析構(gòu)函數(shù) destroy begin close(F)//關(guān)閉日志文件 free(Buffer)//釋放緩沖區(qū) terminate(logthread)//中止釋放日志線程 free(logthread) end 線程函數(shù)體 Execute Begin While not terminate do//循環(huán)直到線程被中止 begin If LogCount > 0 then //緩沖區(qū)中有待處理日志 Writeln(f,Buffer[tail])//寫日志到文件 Tail <- Tail + 1 mod sizeof(buffer)//循環(huán)移動指針 Lock.Acquire//互斥訪問 LogCount <- LogCount-1 Lock.Release//釋放互斥資源 Endif end end 公有成員函數(shù):ADD ADD(狀態(tài)信息)//工作線程(或住線程)調(diào)用,把狀態(tài)信息寫入緩沖區(qū) Begin Lock.Acquire If LogCountBuffer[Head] <- 狀態(tài)信息 Head <- head + 1 mod sizeof(buffer) LogCount <- LogCount + 1 Endif Lock.Release End 在程序的初始化部分創(chuàng)建一個日志類的實例 Log<-ClassLogThread.create 在工作線程中把狀態(tài)信息寫入緩沖區(qū) …… i <- func(j) Log.ADD(timetostr(now) + inttostring(threadID) + “current i is ” + inttostring(i)) …… |
在以上的代碼可以看到,在工作線程中用讀寫內(nèi)存代替磁盤I/O操作,保證了工作線程以及系統(tǒng)的響應(yīng)速度。同時我們也注意到,工作線程把日志寫入緩沖區(qū)時同樣需要線程間的互斥訪問,否則就會產(chǎn)生意想不到的后果。通常,每個工作線程在寫入緩沖區(qū)之前都要鎖定共享變量LogCount和Head,在完成操作時釋放該資源。但由于鎖定的只是共享內(nèi)存變量而非磁盤資源,因此用在同步上的時間開銷同樣很少。為了減少鎖定資源給程序帶來的負面影響,在設(shè)計時應(yīng)注意最小化鎖定對象,盡量把無需鎖定的代碼放在鎖定范圍之外,例如線程函數(shù)Execute中判斷是否有待處理日志時,并沒有鎖定LogCount,同時由于Tail變量和磁盤操作都是日志線程單獨操作,也被放在鎖定范圍之外,因此不會影響其他工作線程的運行。
一些需要注意的事項和提示:
◆在系統(tǒng)開始運行時就應(yīng)分配好緩沖區(qū),并且在線程中讀寫緩沖區(qū)時應(yīng)注意緩沖區(qū)的邊界,否則可能造成內(nèi)存溢出。
◆在代碼中可以看到,當(dāng)緩沖區(qū)滿時,可能會丟棄一些日志,因此為了保存所有日志,應(yīng)該把緩沖區(qū)設(shè)置得盡可能大,同時精心設(shè)計需要日志記錄的關(guān)鍵點,減少不必要的日志記錄。
◆在日志中記錄工作線程的ID,以便在日后分析時方便的提取同一線程的日志。
◆在日志中加時間戳,這樣可方便分析不同線程之間的同步關(guān)系。
◆對于有大量線程同時進行日志記錄的系統(tǒng)來說,這些線程將在獲得和釋放鎖上花費大部分的時間,會影響整體系統(tǒng)的性能。這時可以創(chuàng)建多個日志線程類的實例,各自獨立操作不同的緩沖區(qū)和文件,減少共享沖突的發(fā)生。在工作線程中,把日志分類,根據(jù)不同的日志類型,寫入到不同的緩沖區(qū),再由相應(yīng)的日志線程寫入不同文件。
總結(jié)
以上是生活随笔為你收集整理的实时多线程系统的日志实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么待办事项清单不管用
- 下一篇: Java中获取当前函数名