最近公司專案需要分析日誌,我拿到的日誌經過了一次處理,以json
格式儲存,日誌量每小時大約
1g,行數大約
60萬,此為背景。
其實對於這類問題,通常的解法是寫個指令碼去跑。對於我來說,主業是c/c++
,指令碼就只會
bash
和awk
,可是這兩種都無法直接處理
json
;其他像
python
和perl
可以處理但又不想學。怎麼辦呢?我想到的辦法是用
c++設計乙個小工具,它從標準輸入
stdin
中獲取json
資料,然後取出我們感興趣的字段,最後從
stdout
輸出。這樣就可以在
bash
中利用管道將不同的處理流程串起來。
我首先實現了乙個單執行緒版本,首先使用std::getline
從std::cin
中獲取一行資料,然後解析資料並提取字段,最後通過
std::cout
輸出。使用這個版本處理一次要花大約
5分鐘,當時覺得好像也可以接受了。
後來在和同事聊天時談到這個問題,同事說可以試試多執行緒。我一想,確實是這樣,我們的系統環境擁有12
核cpu
,32g
記憶體,不好好利用實在太可惜了。於是我使用作業系統中經典的生產者
/消費者模型又實現了一版。首先有
1個讀取執行緒,負責從
std::cin
中獲取資料,並將得到的資料投遞到讀取佇列中;接著有
10個解析執行緒,負責從讀取佇列中取出資料,然後解析提取字段,並將結果投遞到輸出佇列中;最後還有
1個輸出執行緒,負責從輸出佇列取出結果並使用
std::cout
輸出。
就在我期待奇蹟發生的時候,奇蹟果然發生了,使用這個版本處理一次大約需要4
分鐘,這顯然是不可接受的。拿著同樣的**在
windows
上執行(
i7 4核8
執行緒,16g
記憶體),結果只需要不到
30秒,後來拿前面的單執行緒版本跑也只需要大約
100秒。那麼問題出在**呢?通過
callgrind
分析後發現,程式卡在讀取執行緒中,具體是
std::getline
這個函式。
在網上查閱部分資料後才知道,c++
為了和c
語言做相容,使用
std::cin
和std::cout
做輸入輸出時會和
stdin
和stdout
做同步,這將消耗大量時間,而這個功能可以通過
std::ios::sync_with_stdio(false)
關閉,條件是不能混用c/c++的輸入輸出了。試了一下,情況果然好了很多,單執行緒版本只要
94秒的樣子,效能比
windows
上略好;多執行緒版本需要
43秒左右,效能比
windows
略差。即使如此,也非常可以接受了。
就在我準備收工的時候,又發現了新的問題:輸出執行緒的結果不正確。正常情況下我們的工具遇到一條輸入資料就會產生一條輸出資料,但實際情況是輸入60
萬條資料,對應的輸出有時多於
60萬,有時少於
60萬。通過對結果仔細分析,發現出現了資料損壞,而這在邏輯上是不可能的。
為什麼說不可能,因為我們的程式已經考慮到了多個執行緒使用std::cout
輸出可能會造成資料損壞的問題,從而專門使用乙個執行緒進行輸出。搜尋整個**,也確實只在輸出執行緒中使用了一次
std::cout
,真是太奇怪了。
追查了許久,終於發現了乙個奇怪的函式std::cin.tie
。帶引數呼叫時用於給
std::cin
繫結乙個輸出流,不帶引數時直接返回當前繫結的物件,而
std::cin
預設繫結的就是
std::cout
,std::cin
在每次讀取之前會先對繫結的輸出流物件執行
flush
操作。問題終於找到了,我們雖然確保了
std::cout
只在乙個執行緒中使用,但是
c++的預設實現使讀取執行緒中
std::cout
也被使用到了,解決辦法就是往
std::cin.tie
中傳入null
引數,解除繫結。
下面附上一段測試**,用於復現這個問題。
#include #include #include #include #include std::atomicrunning(true);
static const char * pmsg[10] = ;
static void writing(void)
running = false;
}static void reading(void)
}int main(int argc, char ** argv)
程式開了兩個執行緒,輸出執行緒會輸出10萬行資料,讀取執行緒只是隨便讀著玩的,使用的時候需要重定向一下輸入輸出,輸入使用乙個稍大一點的文字檔案即可。通過注釋掉main函式前兩行可以試不同條件下的運**況,可以發現乙個有趣的現象,在windows上基本上是沒有差別的,也不會出錯;而linux上差別就大了,呵,自己慢慢體會。
上述**在windows上使用vs2013編譯通過。linux上使用gcc 4.9.2編譯通過,編譯命令:g++ -std=gnu++11 -pthread -o3 test.cpp -o test。
C C的標準輸入輸出流
對於c 只有更深,沒有淺嘗輒止 下面先來看c 的標準輸入輸出流 物件標準流 標頭檔案 include include input stream輸入流 include output stream輸出流iostream庫定義了三個標準流物件 輸出主要由過載的左移操作符 來完成,輸入主要由過載的右移操作符...
C C 標準輸入輸出問題
輸入輸出格式 scnaf 電腦會根據佔位符判斷怎麼讀 如scanf f f a,b 兩個數字用空格隔開就行,因為這裡不是字元型,不會把空格輸進去 但while scanf d c c size,re,edge eof 這裡要不能把空格打上去,會當字元賦給變數 p.s樣例輸入為 11ba 5ab 這種...
標準輸入輸出
linux的大部分命令都具有標準的輸入輸出埠,有哪些標準裝置資訊?名稱 檔案描述符 含義裝置 說明stdin 0標準輸入 鍵盤命令在執行時所需要的輸入資料,通過它來獲取 stdout 1標準輸出 顯示器命令在執行後所產生的輸出資料,通過它來送出 stderr 2標準錯誤 顯示器命令在執行後所產生的錯...