一道值得思考的fork()面試題

程序如下,判斷輸出多少個'_'

./a.out

int main(){
    for(int i = 0; i < 2; ++i){
        fork();
        printf("_");
    }        
}

熟悉fork的話,這裏很容易就能知道,一共產生了3個子進程,還有一個父進程,所以一共是四個進程;每次fork之後都會輸出一個'_',那麼在這裏應當的輸出是6個'_'

 

 

 

 但是實際輸出卻是 8個'_'; 但是如果在printf("_")之後使用fflush(stdout),或者使用printf("\n")清空輸出緩衝區來輸出則結果就是6個了,這就不得不再返回來談一談printf的輸出機制。

  • 標準I/O對待緩存的數據採用3種不同的策略,全緩衝、行緩衝、無緩衝。
  • 對於沒有交互的終端,例如塊設備文件,系統採用全緩衝;(比如輸出到文件)
  • 對於標準輸入,標準輸出這樣交互設備採用行緩衝;(輸出到屏幕或控制檯)
  • 對於需要立即響應的設備,例如標準錯誤採用無緩衝;

printf函數只有當緩衝區被刷新的時候纔會輸出數據,在此之前只是將數據存放到緩衝區。

理解了這點後,再去分析上述代碼;首先只有一個進程a.out,緩衝區爲空;第一次fork()後,父進程a.out產生了一個子進程“子1”,子進程複製父進程的緩衝區(此時爲空),然後printf將"_"先放入緩衝區內,a.out與"子1"的緩衝區內各有一個"_";

第二次fork()後,a.out產生了一個子進程"子2",“子1”產生了一個子進程“子3”,“子2”和“子3”均從各自的父進程複製了緩衝區的“_”,此時,a.out,"子1",“子2”,“子3”各自的緩衝區內均有一個“_”,接下來的printf語句又將”_“分別放入了四個進程的緩衝區內(圖中用黑色標識的_),此時,循環結束,四個進程的各自緩衝區內都分別有兩個"_",總共8個,特殊之處在於,”子2“與”子3“的緩衝區內的第一個”_“是複製各自的父進程緩衝區得到的,a.out與"子1"的緩衝區內的第一個"_"是printf累積”_“的結果。如此一來,循環結束後,程序就要結束了,此時緩衝區刷新輸出了8個"_".

 

 到這裏,這道題已經講的很明白了,爲了深入理解,再通過幾個代碼詳細理解一下緩衝區的刷新機制:

int main(){
    printf("hello");
//fflush(stdout); sleep(
5); int m = 5; m = 7; while(m-->0){ printf("*"); } //printf("\n"); sleep(3); printf("world"); }

這個程序將如何輸出呢?如果去掉註釋輸出結果又是什麼?先思考一下吧。

理想中的輸出應該是先輸出 "hello",等待5秒,輸出7個'*',再等待3秒,輸出"world";但是實際情況卻是等待 8秒,一瞬間輸出”hello*******world“.

如果去掉註釋呢?就是我們所期盼的第一種情況了;造成這種現象的原因上面已經提到了,就是printf並非直接輸出,而是先攢到緩衝區裏,等待緩衝區刷新再輸出;這段程序中的兩條註釋語句均可以刷新行緩衝策略的緩衝區(行緩衝與全緩衝在文章末尾)。

那麼來看一下緩衝區刷新的時機吧

  • 1.遇到“\n”,立即刷新緩衝區。(行緩衝)
  • 2.程序調用fflush函數刷新緩衝區
  • 3.程序以exit結束,緩衝區會刷新。如果以_exit結束,緩衝區數據會被直接清空。
  • 4.緩衝區滿,也會將緩衝區數據刷新出來。

這樣子便不難理解了吧。可惜剛開始學習printf的時候總是習慣性在後面加一個'\n',所以這麼久了這麼重要的一個緩衝區機制居然才知道。

最後再用一個例子補充一下行緩衝與全緩衝的區別

int main(){
    printf("hello world\r\n");
    if(0 == fork()){
        printf("son\r\n");
    }else{
        printf("father\r\n");
    }                                                                           
}

先普通執行一下輸出結果爲一條"hello world",一條"son",以及一條"father";

再將輸出位置重定向到文件,輸出結果多了一條"hello world"

 

 

 

 同樣的程序,只因爲輸出位置的不同,結果也就不同,這是因爲第一種使用了標準輸出也就是控制檯和屏幕,採用行緩衝策略;第二種將標準輸出重定向到文件,採用的是全緩衝策略;行緩衝策略中的'\n'對全緩衝無效;所以又回到了文章開始的那道面試題的緩衝區複製。由於是全緩衝,並不會刷新緩衝區,因此fork時子進程複製了父進程的緩衝區,裏面有一條”hello world“,此時父子進程再各自往緩衝區中攢了"father"和'son';因此就呈現出最終的輸出結果啦。

(這種緩衝區機制在C++中的cout也是一樣的)

完。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章