Python管道 - 逐步读取输出时发生了什么

0 投票
3 回答
980 浏览
提问于 2025-04-15 15:18

根据这本书中的一部分内容,

管道的一个常见用法是逐步读取压缩文件;也就是说,不需要一次性解压整个文件。下面这个函数接受一个压缩文件的名字作为参数,并返回一个管道,这个管道使用gunzip来解压内容:

 def open_gunzip(filename):
     cmd = 'gunzip -c ' + filename
     fp = os.popen(cmd)
     return fp

如果你一次从fp读取一行,你就不需要在内存或磁盘上存储解压后的整个文件。

也许我理解错了,但我看不出这是怎么可能的。Python应该没有办法在gunzip输出结果的过程中暂停吧?我想gunzip不会在读取一行输出之前就一直阻塞,所以一定有某个缓冲区在捕捉这些内容(无论是在Python解释器内部,还是在操作系统中,无论是在内存还是在磁盘上),这意味着解压后的文件一定是存储在某个地方的,对吧?

3 个回答

0

不是Python在暂停gunzip,而是操作系统内核会在gunzip尝试写入数据时(使用write()这个系统调用)停止执行,因为它的缓冲区已经满了。这种情况叫做IO阻塞。内核会维护一个内部缓冲区,连接管道的两端,这个缓冲区和任何正在写入或读取管道的进程的缓冲区是独立的。

同样,当Python从一个空的管道读取数据时,也会出现阻塞的情况,也就是说,这个管道里没有gunzip写入的数据。

管道可以被看作是解决生产者-消费者问题的一种方法。

1

这其实是来自于 gunzip 的实现,而不是来自 Python。它是用 C 语言写的。它可能使用 C 语言的 stdio.h 中的 fwrite() 来输出数据。

我使用的 libc6 实现会自动创建一个输出缓冲区,当这个缓冲区满了之后,就会在 fwrite() 这里停下来,等到可以写入更多数据时再继续。

2

你的想法有点问题。gunzip这个工具并不需要看到整个文件才能解压。你可以去看看解压文件的格式,里面有一个目录,记录了各个部分的位置。

其实可以分块解压一个文件。

“解压后的文件一定是完整存储在某个地方吧?”

不一定。其实不太明白你为什么会这么想,或者你是从哪里看到的。

所有底层的输入输出操作都有可能会被阻塞。gunzip在写入管道的时候,如果管道的缓冲区满了,就会被阻塞。这是管道输入输出的特性,管道的输入输出会阻塞。

想了解更多细节,可以查一下管道的手册。

如果一个进程试图从一个空的管道读取数据,那么read(2)会
被阻塞,直到有数据可用。如果一个进程试图写入一个
满的管道(见下文),那么write(2)会被阻塞,直到从管道中
读取到足够的数据,以便完成写入。通过使用fcntl(2)
的F_SETFL操作来启用O_NONBLOCK打开文件状态标志,可以实现非阻塞
输入输出。

撰写回答