為了分析軟體系統在測試和執行期產生的故障,目前大多數軟體系統所廣泛使用的一種方法就是日誌記錄。但系統在儲存日誌時需要大量的磁碟 i/o操作,同時由於多執行緒併發互斥訪問檔案系統可能造成的阻塞,會引起實時軟體系統的效能下降,嚴重時造成整個系統停止響應。本文給出了利用迴圈緩衝區和單獨的日誌讀寫執行緒實現實時系統的日誌功能。
「如果有兩種方式可以編寫出沒有錯誤的程式,那麼只有第三種方式是有效的。」 —algol語言之父alan j. perlis。
任何軟體系統,都不可避免的存在設計上的bug,發現bug和解決bug貫穿於軟體系統的整個生命期。當軟體系統產生非預期的結果時,分析故障原因成為首要任務。對於非實時的單執行緒程式,可採取設定斷點、單步跟蹤等手段,能比較容易地確定故障點。然而對於實時的多執行緒軟體系統來說,這些方法不能滿足實時性要求,從而在除錯過程中往往使系統變得不可用。因此日誌記錄成為關鍵的計算機應用系統的生存期中一件非常重要的活動。通過分析在系統出現故障時的日誌,即可確定故障原因。在執行過程中,系統將不斷地產生跟蹤資料,並將其寫入到磁碟上的文字檔案中。通常的做法是在程式執行的關鍵點把系統的狀態資訊寫入磁碟,然而這種做法的***就是,由於磁碟i/o操作所耗費的時間往往遠大於系統中實際工作執行緒的執行時間,甚至一次磁碟操作消耗的時間比乙個執行緒的整個生命期還要長,這就影響了執行緒的執行速度,再考慮到執行緒間的同步執行,其他執行緒可能會等待該執行緒的執行結果,這樣就會造成整個系統效能的下降,不能滿足實時系統的要求。同時由於可能不只乙個執行緒在記錄執行日誌,因此會有多個執行緒併發的訪問磁碟,造成執行緒間的長時間阻塞和等待,更大大影響了系統執行時的響應速度,嚴重時可能造成系統停止響應。這種情況在實時性要求高的系統中是不允許的。
筆者在最近為ipswitch系統開發的通訊網關中,利用日誌執行緒和迴圈緩衝區解決了上述問題。本文把所有用來實現系統自身功能的執行緒統稱為「工作執行緒」,用於讀寫磁碟記錄日誌的執行緒稱為「日誌執行緒」,乙個可迴圈使用、被所有執行緒共享的固定大小的記憶體區域稱為「迴圈緩衝區」。
在工作執行緒中,在需要記錄日誌的地方把程式執行的狀態資訊寫入迴圈緩衝區,再由單獨的日誌執行緒讀出並寫入磁碟儲存。在工作執行緒把日誌寫入緩衝區時占用的時間很少,基本不影響工作執行緒執行。在日誌執行緒進行磁碟i/o操作時,不占用工作執行緒的執行時間,同時由於只有乙個單獨的日誌執行緒對單個日誌檔案進行讀寫,因此完全避免了由於併發操作同一檔案而造成的執行緒間的阻塞。
以下是實現上述功能的偽**(根據物件導向思想設計成日誌類):
日誌類說明class classlogthread
private:
var
head//迴圈緩衝區寫入指標
tail//迴圈緩衝區讀取指標
logcount//緩衝區中待處理日誌記數器
buffer[1024]//迴圈緩衝區(大小可根據實際情況在這裡是乙個字串陣列,陣列中每個元素代表一行日誌)
lock//同步變數,用於多執行緒同時讀寫迴圈緩衝區併發控制
f//日誌檔案控制代碼
procedureexecute//執行緒函式體,實現日誌執行緒功能
public:
procedureadd(狀態資訊)//工作執行緒呼叫,把狀態資訊寫入緩衝區
日誌類實現
建構函式
create
begin
getmem(buffer)//申請緩衝區
head <- 0 //初始化指標
tail <- 0
logcount <- 0//初始化待處理日誌記數器
f <- open(filename)//建立或開啟日誌檔案
createthread(logthread,execute)//建立日誌執行緒並指定執行緒函式
end
析構函式
destroy
begin
close(f)//關閉日誌檔案
free(buffer)//釋放緩衝區
terminate(logthread)//中止釋放日誌執行緒
free(logthread)
end
執行緒函式體
execute
begin
while not terminate do//迴圈直到執行緒被中止
begin
if logcount > 0 then //緩衝區中有待處理日誌
writeln(f,buffer[tail])//寫日誌到檔案
tail <- tail + 1 mod sizeof(buffer)//迴圈移動指標
lock.acquire//互斥訪問
logcount <- logcount-1
lock.release//釋放互斥資源
endif
end
end
公有成員函式:add
add(狀態資訊)//工作執行緒(或住執行緒)呼叫,把狀態資訊寫入緩衝區
begin
lock.acquire
if logcountbuffer[head] <- 狀態資訊
head <- head + 1 mod sizeof(buffer)
logcount <- logcount + 1
endif
lock.release
end
在程式的初始化部分建立乙個日誌類的例項
log<-classlogthread.create
在工作執行緒中把狀態資訊寫入緩衝區
i <- func(j)
log.add(timetostr(now) + inttostring(threadid) + 「current i is 」 + inttostring(i))
在以上的**可以看到,在工作執行緒中用讀寫記憶體代替磁碟i/o操作,保證了工作執行緒以及系統的響應速度。同時我們也注意到,工作執行緒把日誌寫入緩衝區時同樣需要執行緒間的互斥訪問,否則就會產生意想不到的後果。通常,每個工作執行緒在寫入緩衝區之前都要鎖定共享變數logcount和head,在完成操作時釋放該資源。但由於鎖定的只是共享記憶體變數而非磁碟資源,因此用在同步上的時間開銷同樣很少。為了減少鎖定資源給程式帶來的負面影響,在設計時應注意最小化鎖定物件,盡量把無需鎖定的**放在鎖定範圍之外,例如執行緒函式execute中判斷是否有待處理日誌時,並沒有鎖定logcount,同時由於tail變數和磁碟操作都是日誌執行緒單獨操作,也被放在鎖定範圍之外,因此不會影響其他工作執行緒的執行。
多執行緒的實現原理(二)
synchronized 的實現原理和應用 synchronized有三種方式來加鎖 1.修飾例項方法,兩個執行緒同時訪問同乙個例項物件中的方法 synchronized this 時候會發生有乙個會被阻塞。public class demo extends thread catch interru...
多執行緒的實現
include include include include include include include include include include include void client void arg connfd else if ret 0 printf buf s n buf r...
多執行緒的實現
通常情況下,應用程式都是在乙個執行緒中執行操作。但是,當呼叫乙個耗時操作 例如,大批量i o或大量矩陣變換等cpu密集操作 時,使用者介面常常會凍結。而使用多執行緒可以解決這一問題。qt有兩種多執行緒的方法,一種是繼承qthread的run函式 另外一種是把乙個繼承於qobject的類轉移到乙個th...