C C 標準輸入輸出的坑

2021-07-02 20:52:39 字數 2820 閱讀 9615



最近公司專案需要分析日誌,我拿到的日誌經過了一次處理,以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標準錯誤 顯示器命令在執行後所產生的錯...