关于Unix程序管道通讯的Buffer大小

November 11, 2008 at 5:56 pm (linux, python, vim)

事情缘起于使用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\ %,搞定。

Permalink 4 Comments

在vim中让:make智能调用ant和make

October 24, 2008 at 9:47 am (vim)

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++程序一样的感觉了?

Permalink Leave a Comment

Follow

Get every new post delivered to your Inbox.