关于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\ %,搞定。