2014-02-11 43 views
1

我有一個使用的記錄標準COUT,例如多線程應用程序,重定向COUT的每個線程文件管理線程安全記錄

cout << "some text" << endl; 

的問題是,該計劃正面臨着一個偶然的碰撞因爲多線程非線程安全地訪問共享cout。

說,我的程序被命名爲prg.exe,我們運行作爲prog.exe> t.log

由個體線程產生被混到了,甚至沒關係,壞的部分是崩潰每當有所有日誌競爭線程在訪問cout時發生了爭用,一個嘗試刷新,另一個嘗試將某些東西放在那裏,導致崩潰。

由於在現有代碼中cout有很多用途,被不同的線程使用,所以很難將所有的cout改爲別的。所以,我想通過以下:

std::streambuf * redirect_output(char * filenm, std::ofstream& filestr) 
{ 
    std::streambuf *newsb, *oldsb; 
    filestr.open(filenm); 
    oldsb = std::cout.rdbuf();  // back up cout's streambuf 
    newsb = filestr.rdbuf();  // get file's streambuf 
    std::cout.rdbuf(newsb);  // assign streambuf to cout 
    return oldsb; 
} 

void restore_output(std::streambuf * oldsb, std::ofstream& filestr) 
{ 
    std::cout.rdbuf(oldsb);  // restore cout's original streambuf 
    filestr.close(); 
} 
void showFileContent(char *filenm) 
{ 
    std::ifstream infile; 
    infile.open(filenm); 
    //read data from file 
    cout << "------------- " << filenm << " -------------" << endl; 
    cout << infile.rdbuf(); 
    infile.close(); 
} 

每個線程,在啓動,試圖調用redirect_output來清點重定向到一個文件,每個線程單獨的文件。例如,如果我們有3個線程,我們有t1.log,t2.log,t3.log ,最後我們調用每個線程的restore_output,最後在main中,我們通過

合併單個日誌文件
showFileContent("t1.log"); 
showFileContent("t2.log"); 
showFileContent("t3.log"); 

我的問題是,是否明智和安全地重定向到每個線程的單個日誌文件併合並在主函數的末尾? 我們可以通過單獨的線程在邏輯同步點合併各個日誌文件嗎?

其他選項可以是線程安全的singleton類封裝內置的I/O並使用該單例對象而不是cout例如, SPCL_IOSTREAM :: getOStream()

+0

根據[這個答案](http://stackoverflow.com/a/6374525/596781),你的前提是不正確的。 –

回答

1

儘管提到你有很多使用std :: cout進行日誌記錄的用戶,我建議你看看here來考慮你是否真的想遵循這個策略。 我的建議不是重定向cout,而是提供一個同步記錄器類,它也可以提供輸出選項,並使用它。

// Logger() is a singleton that returns a ref. to a different output stream 
// based on its parameter. 
// Default (no parameter) send everything to cout after synchronizing threads 
Logger::addLog("log.txt"); // add an instance of a stream in a static 
          // map<string, ofstream> 

// The dtor can delete the streams after flusing, or for better control, you can 
// add methods like 
Logger::delLog("log.txt"); // flush and erase the map entry, this log is gone 

Logger::log(0, "Some message"); 
Logger::log("log.txt", "another message"); 

// Logger::log() can also be a variadic method that takes a log key, a format, 
// and parameters like printf 
Logger::log(0, "Value=%d", int_value); 

你甚至可以考慮通過返回鎖定在它的構造函數(日誌流互斥一個仿函數,以改善它),並保持一個裁判給它的數據成員,然後有一個operator(),它提供訪問流,並在其dtor()解鎖。這種方法可以讓你使用流支持的運算符,但是,你需要確保函數每次都被銷燬,以避免死鎖。

編輯後評論:

是的,你可以重定向它。看看StackOverflow中的這個回答here。 另外,如果您還希望在傳統代碼中捕獲任何printf()調用,則需要使用dup2()在stdio級別重定向。相關鏈接herehere

希望這會有所幫助。

+0

我欣賞@DNT。但是,該應用程序具有巨大的遺留代碼,涉及std :: cout進行日誌記錄。我在想如何保留現有的std :: cout,並在下面做魔術? std :: stringstream可以用來保存日誌記錄推送到std :: cout,稍後由一個(主)線程使用來整理並推送到實際的std :: cout的streambuf?或者其他方式? –

+1

我想在此添加一條評論。如果您有權訪問整個舊版源代碼,GabiMe所說的可能是更好的解決方案。的確,收集日誌可能會導致數據丟失,如果在合併之前崩潰並將數據清除到文件。 – DNT

3

這是搜索/替換(或sed)是您最好的朋友的情況之一。只需花時間將std::cout用法與調用替換爲您最喜歡的(線程安全)日誌庫,併爲您節省很多麻煩。

還在main()結尾處合併文件聽起來很複雜(您需要根據每個條目的時間戳進行合併)才能維護順序 - 而且不可靠 - 如果在main()的末尾崩潰,該怎麼辦?