关于Unix程序管道通讯的Buffer大小
事情缘起于使用vim编辑python程序。通常python程序员都会设置makeprg为python %。这样可以通过:make自动的对当前编辑的文件进行测试。对于大多数情况,程序执行时间很短,因此不会有什么问题。今天我突然发现长时间运行的程序,:make后很难及时的获得输出。
开始我以为是sleep系统调用造成的。我们知道linux下shellpipe通常设定为”2>&1|tee”,为的是可以同时在stdout输出和保存文件供quickfix调用。这里的tee就是完成这样的操作。将输入管道中的数据拷贝到多个输出管道中去。
那么问题出现了。Unix的管道不同于标准输入输出,当输入换行符时并不会flush管道的缓冲区(即non-linebufferd)。这样当源程序的标准输出被重定向目标程序的时候,无法获得像标准输出那样的效果。通常Unix管道用来批量处理大量的数据,这样的设计不会有问题,还会颇具效率。但是当我们需要管道的数据最终输出到stdout或者其他用于显示的程序时,源程序的输出就没办法获得实时的显示。比如说我们要用vim获取一个python写的守护进程的输出,这样可以通过quickfix定位异常时,这个机制就显得十分不便。
由于管道至少需要两个程序互相配合,因此我最先怀疑到了tee。tee的实现非常简单,其核心流程在下面几行代码中:
for (exitval = 0; *argv; ++argv)
if ((fd = open(*argv, append ? O_WRONLY|O_CREAT|O_APPEND :
O_WRONLY|O_CREAT|O_TRUNC, DEFFILEMODE)) 0)
for (p = head; p; p = p->next) {
n = rval;
bp = buf;
do {
if ((wval = write(p->fd, bp, n)) == -1) {
warn("%s", p->name);
exitval = 1;
break;
}
bp += wval;
} while (n -= wval);
}
可以看到,tee(BSD实现)每次从输入流读入的一点数据,都是立即写出到链表中的每个输出流去的。只要缓冲区中存在数据,循环就不会结束。除非有两种情况,一是在输入流时阻塞,二是在输出流段阻塞。对于我们的情况,输出流重定向给了vim的stdin是行缓冲的,不可能出现阻塞。那么之后在输入流,也就是python这端了。对于python这端,简易的做法是print后用std.stdout.flush()刷新输出的缓冲区。但是对于第三方模块的输出,还需要一个更完美的解决方案。
google下发现python支持一个-u命令,完全关掉管道的buffering(靠,绕了一大圈)。顺便看可一下python的代码,发现是通过setvbuf实现的:
if (unbuffered) {
#ifdef MS_WINDOWS
_setmode(fileno(stdin), O_BINARY);
_setmode(fileno(stdout), O_BINARY);
#endif
#ifndef MPW
#ifdef HAVE_SETVBUF
setvbuf(stdin, (char *)NULL, _IONBF, BUFSIZ);
setvbuf(stdout, (char *)NULL, _IONBF, BUFSIZ);
setvbuf(stderr, (char *)NULL, _IONBF, BUFSIZ);
#else /* !HAVE_SETVBUF */
setbuf(stdin, (char *)NULL);
setbuf(stdout, (char *)NULL);
setbuf(stderr, (char *)NULL);
#endif /* !HAVE_SETVBUF */
#else /* MPW */
/* On MPW (3.2) unbuffered seems to hang */
setvbuf(stdin, (char *)NULL, _IOLBF, BUFSIZ);
setvbuf(stdout, (char *)NULL, _IOLBF, BUFSIZ);
setvbuf(stderr, (char *)NULL, _IOLBF, BUFSIZ);
#endif /* MPW */
}
else if (Py_InteractiveFlag) {
#ifdef MS_WINDOWS
/* Doesn't have to have line-buffered -- use unbuffered */
/* Any set[v]buf(stdin, ...) screws up Tkinter :-( */
setvbuf(stdout, (char *)NULL, _IONBF, BUFSIZ);
#else /* !MS_WINDOWS */
#ifdef HAVE_SETVBUF
setvbuf(stdin, (char *)NULL, _IOLBF, BUFSIZ);
setvbuf(stdout, (char *)NULL, _IOLBF, BUFSIZ);
#endif /* HAVE_SETVBUF */
#endif /* !MS_WINDOWS */
/* Leave stderr alone - it should be unbuffered anyway. */
}
okay,学习了。最后把python.vim中的set makeprg设成python\ -u\ %,搞定。
在vim中让:make智能调用ant和make
vim是我最喜欢的编辑器。从前写的程序都是c/c++的,使用vim编辑非常的方便。最近因为项目需要,转为使用java开发。语言虽然换了,但是还是希望使用一致的开发环境。
使用Vim开发java项目除了网上其他人提及的设置之外,还有一点重要的地方就是:make命令对编译工具的调用。由于C/C++和Java项目使用不同的编译和管理系统,因此简单的更改makeprg变量的设置,是不能实现C/C++Java开发环境之间的一置性的。为此vimtips上有人提供了一个专门的vim脚本,可以根据目录下的特征文件(如Makefile,build.xml等)的存在来判定使用make还是ant进行编译管理。但是试用后觉得并不完美。在split后的文件中执行:make并不能准确的设置makeprg的值。
于是个人想到了一个使用SHELL脚本来区分目录所在项目,再分别调用对应的程序来make。经过测试发现效果比较理想。首先编写一个脚本smartmake,放在/usr/local/bin目录下:
#!/bin/sh MAKEPROG="make" if [ -f build.xml ] then MAKEPROG="ant" fi $MAKEPROG $*
然后在.vimrc中加入如下设置:
set makeprg=smartmake
这样,可以vim设置快捷键轻易的调用make和ant,而且可以向make和ant传递任意参数。有兴趣的可以试一下,现在些java程序是不是和c/c++程序一样的感觉了?