关于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

Follow

Get every new post delivered to your Inbox.