我们耳熟能详的操作系统:Linux、Windows以及一些数通设备(Huawei交换机等)都是支持管道符(|)的。
那么管道是如何工作的呢?跟我们“自认为”、“应如是”是否有出入呢?
结合AI给的解释,我们配合实验操作来看一下(看到最后会打破你固有认知哦)。
管道工作流程分解
以下是cmd1 | cmd2 | cmd3的完整执行流程:
| 步骤 | 操作 | 关键点 |
|---|---|---|
| 1. 解析与准备 | Shell识别管道符号|,创建无名管道和进程组 | 管道本质上是内核提供的一段缓冲区(通常4KB) |
| 2. 进程创建 | Shell为每个命令创建子进程(fork) | 所有子进程同时启动,形成管道链 |
| 3. 文件描述符重定向 | Shell设置每个进程的标准流: • cmd1:stdout → 管道写端• cmd2:stdin ← 管道读端,stdout → 新管道写端• cmd3:stdin ← 管道读端,stdout → 终端 | 关键:每个命令不知道自己在管道中,像正常程序一样读写 |
| 4. 并发执行 | 所有命令并行运行,不是顺序执行 | cmd2在cmd1产生输出前就会启动并等待输入 |
| 5. 数据传输 | 数据流经管道缓冲区,像水流过水管 | 如果读者慢,写者会阻塞;如果读者快,会等待数据 |
| 6. 进程终止 | 命令按自己的节奏结束 | 退出状态各自独立生成,互不影响 |
| 7. 状态收集 | Shell收集所有子进程的退出状态 | 默认:只取最后一个(cmd3)的状态作为管道状态 |
针对如上的讲解,基本上都好理解。只有一点,管道串联的命令启动和运行不是顺序的,而是并行的。这个怎么验证一下子呢?
使用如下命令:
#!/bin/bash echo "=== 实验1:时间戳验证并行执行 ===" # 生产者:每秒产生一个时间戳 # 消费者:立即处理并打上自己的时间戳 ( echo "生产者启动: $(date +%T.%N)" for i in {1..5}; do echo "数据$i@$(date +%T.%N)" sleep 1 done echo "生产者结束: $(date +%T.%N)" ) | ( echo "消费者启动: $(date +%T.%N)" while read line; do echo "消费者处理 [$(date +%T.%N)]: $line" # 消费者处理速度快,没有延迟 done echo "消费者结束: $(date +%T.%N)" )按照我们的固有理解,“生产者”肯定要最早运行,然后生产完所有数据,传递给“消费者”,消费者消费“生成数据”。但是,这是错误的哦。下面是shell脚本运行回显:
消费者启动: 11:53:59.614424713
消费者处理 [11:53:59.615773820]: 生产者启动: 11:53:59.614495023
消费者处理 [11:53:59.617394235]: 数据1@11:53:59.615758632
消费者处理 [11:54:00.620142390]: 数据2@11:54:00.618415176
消费者处理 [11:54:01.622494366]: 数据3@11:54:01.621038623
消费者处理 [11:54:02.625012448]: 数据4@11:54:02.623514739
消费者处理 [11:54:03.627571099]: 数据5@11:54:03.626168602
消费者处理 [11:54:04.630431399]: 生产者结束: 11:54:04.628782660
消费者结束: 11:54:04.631653786
可见,消费者启动时间要比生产者启动要早,此后,每隔1s生产一条数据,消费者就会消费1条数据,而不是等所有数据都生产完。