zoukankan      html  css  js  c++  java
  • 一次进程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

  • 相关阅读:
    黑色边影,
    拉伸的代码,
    一定是selection的原因啊,要不然呢,
    status bar的差别,
    黄色,
    域名错了,
    node=day4
    PS切片
    移动端插件IScroll.js
    移动web资源概论
  • 原文地址:https://www.cnblogs.com/cchust/p/3899832.html
Copyright © 2011-2022 走看看