这两天有同学使用数据校验工具时发现进程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

最新文章

  1. SELECT CAST(GETDATE() AS VARCHAR(10)) 显示不同格式的原因
  2. DNS
  3. [bzoj4551][Tjoi2016][Heoi2016]树
  4. velocity常用语句速查表
  5. 亲测!阿里云公共DNS,感觉不错!
  6. 将java的class文件放到一个指定文件夹下
  7. RocketMQ学习记录
  8. [AngularJS] Using AngularJS interceptors with $http
  9. javascript创建对象的7种方式
  10. ASP.NET菜鸟之路之Seesion小例子
  11. 注解配置 ssh
  12. c++模板入门
  13. java项目log4j日志打印配置
  14. ArcGIS API for JavaScript 4.2学习笔记[8] 2D与3D视图同步
  15. 实验-使用VisualVM或JConsole进行对程序进行性能分析
  16. 去重是distinct还是group by?
  17. java web中验证码生成的demo
  18. 【bzoj4347】[POI2016]Nim z utrudnieniem dp
  19. c语言学习5
  20. ASP.NET MVC4 Razor

热门文章

  1. LeetCode124:Binary Tree Maximum Path Sum
  2. hibernate---注解--CascadeType属性
  3. spring 使用redis集群配置
  4. BZOJ 4619 Swap Space 解题报告
  5. Greenplum(GP)常见问题,注意事项
  6. MySQL配置文件mysql.ini参数详解、MySQL性能优化
  7. cnodejs社区论坛3--发表话题
  8. ADO.NET操作数据库(一)
  9. Vue-router中文教程-Vue-router参考手册.CHM
  10. InstantClick – 快速响应!让你的网站与众不同