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