一次進程hang住問題分析。。。


      這兩天有同學使用數據校驗工具時發現進程hang住了,也不知道什么原因,我簡單看了看進程堆棧,問題雖然很簡單,但能導致程序hang住,也一定不是小問題。簡單說明下程序組件的結構,程序由兩部分構成,dbchk和dbchk_inner,dbchk采用python代碼實現,dbchk_inner采用C語言實現。dbchk負責並發控制,dbchk_inner則負責具體的校驗任務。用戶通過運行dbchk命令即可達到校驗的目的。進程關系如下:

$ pstree 18649

dbchk─┬─sh───dbchk_inner───2*[{scandiff}]

       └─{dbchk}

    回到問題本身,我用測試用例復現了hang住了場景,查看了dbchk和dbchk_inner的堆棧信息,信息如下:

dbchk進程18649堆棧信息:

$ pstack 18649

Thread 2 (Thread 0x7f4343fff700 (LWP 18658)):

#0 0x000000346f80f09d in waitpid () from /lib64/libpthread.so.0

#1 0x000000347190ff8a in ?? () from /usr/lib64/libpython2.6.so.1.0

#2 0x00000034718de706 in PyEval_EvalFrameEx () from /usr/lib64/libpython2.6.so.1.0

#3 0x00000034718e0797 in PyEval_EvalCodeEx () from /usr/lib64/libpython2.6.so.1.0

 

dbchk_inner進程18660堆棧信息:

pstack 18660

#0 0x000000346f4da3dd in write () from /lib64/libc.so.6

#1 0x000000346f470fd3 in _IO_new_file_write () from /lib64/libc.so.6

#2 0x000000346f470e9a in _IO_new_file_xsputn () from /lib64/libc.so.6

#3 0x000000346f46705d in fwrite () from /lib64/libc.so.6

#4 0x00000000004136f0 in Scanner::run(unsigned int) ()

可以看到父進程dbchk在卡在waitpid()函數,這個容易理解,它應該在等待子進程dbchk_inner結束;再看子進程dbchk_inner,dbchk_inner卡在fwrite()函數,這個就有點奇怪了,為啥寫會被阻塞呢?首先想到的是磁盤空間不夠了?看了下磁盤空間還有很大的剩余,那還有什么可能導致write卡住,還有一種可能就是緩沖區滿了,寫不下去。

基於這個思考,回頭看看dbchk的代碼

pio = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).wait()

可以看到程序里使用了Popen的wait函數,這可以解釋父進程為啥會卡住,因為子進程沒有執行完;注意Popen的參數,將stdout和stderr輸出重定向到了subprocess.PIPE,這個值表示父子進程之間的管道。那么子進程寫緩沖區卡住,應該就是因為PIPE的緩沖區滿了。為啥會滿呢,一是產生的數據太多;另一方面是沒有進程去緩沖區去取數據,導致緩沖區只進不出。PIPE緩沖區默認值大小4096個字節,這個可以通過ulimit -a得到,8*512=4096字節,並且這個值不可以修改的,因為值是定義在linux的頭文件里面,除非你重新編譯linux內核。

$ ulimit -a

core file size (blocks, -c) 0

……

pipe size (512 bytes, -p) 8

 

好了問題找到了,PIPE緩沖區滿是罪歸禍首,如何解這個問題?

1.不將stdout和stderr重定向管道,直接輸出

2.程序控制輸出到管道數據的大小

管道在進程間通信(IPC)使用很廣泛,shell命令就使用的很廣泛。比如:

ps –aux | grep mysqld

上述命令表示獲取mysqld進程相關的信息。這里ps和grep兩個命令通信就采用了管道。管道有幾個特點:

1.      管道是半雙工的,數據只能單向流動,ps命令的輸出是grep的輸出

2.      只能用於父子進程或兄弟進程通信,這里可以認為ps和grep命令都是shell(bash/pdksh/ash/dash)命令的子進程,兩者是兄弟關系。

3.      管道相對於管道兩端的進程而言就是一個文件,並且只存在於內存中。

4.      寫入端不斷往管道寫,並且每次寫到管道末尾;讀取端則不斷從管道讀,每次從頭部讀取。

      到這里大家可能會有一個疑問,管道兩端的進程,寫入進程不斷的寫,讀取進程不斷的讀,那么什么時候結束呢?比如我們剛剛這個命令很快就結束了,它的原理是怎么樣的呢?對於管道,這里有兩個基本原則:

1.當讀一個寫端已經關閉的管道時,在所有數據被讀取后,read返回0,以指示達到文件結束處。

2.當寫一個讀端已經關閉的管道時,會產生sigpipe信息。

結合這個例子,當ps寫管道結束后,就會自動關閉,此時grep進程read就會返回0,然后自動結束。

 

參考文檔:

《UNIX環境高級編程》

http://blog.chinaunix.net/uid-26833883-id-3227144.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM