AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 685305
Accepted
Marcus Müller
Marcus Müller
Asked: 2022-01-07 04:27:44 +0800 CST2022-01-07 04:27:44 +0800 CST 2022-01-07 04:27:44 +0800 CST

Linux 命名管道:不像想象的那样先进先出

  • 772

简而言之:

mkfifo fifo; (echo a > fifo) &; (echo b > fifo) &; cat fifo

我所期望的:

a
b

因为第一个echo … > fifo应该是第一个打开文件的,所以我希望该进程是第一个写入它的进程(首先打开它的解锁)。

我得到什么:

b
a

令我惊讶的是,这种行为也发生在打开两个单独的终端以在绝对独立的进程中进行写入时。

我是否误解了命名管道的先进先出语义?

斯蒂芬建议添加延迟:

#!/usr/bin/zsh
delay=$1
N=$(( $2 - 1 ))
out=$(for n in {00..$N}; do
  mkfifo /tmp/fifo$n
  (echo $n > /tmp/fifo$n) &
  sleep $delay
  (echo $(( $n + 1000 )) > /tmp/fifo$n )&
  # intentionally using `cat` here to not step into any smartness
  cat /tmp/fifo$n | sort -C || echo +1
  rm /tmp/fifo$n
done)
echo "$(( $res )) inverted out of $(( $N + 1 ))"

现在,这 100% 正确(delay = 0.1, N = 100)。

尽管如此,mkfifo fifo; (echo a > fifo) &; sleep 0.1 ; (echo b > fifo) &; cat fifo手动运行几乎总是会产生相反的顺序。

事实上,即使复制和粘贴for循环本身也有一半的时间会失败。我对这里发生的事情感到非常困惑。

linux fifo
  • 2 2 个回答
  • 3542 Views

2 个回答

  • Voted
  1. Best Answer
    Stephen Kitt
    2022-01-07T04:31:18+08:002022-01-07T04:31:18+08:00

    这与管道的 FIFO 语义无关,也无法证明它们的任何内容。这与 FIFO 在打开时阻塞直到它们被打开以进行写入和读取的事实有关。cat所以在打开fifo阅读之前什么都不会发生。

    因为第一个echo应该是第一个。

    在后台启动进程意味着您不知道它们何时实际被调度,因此不能保证第一个后台进程会在第二个后台进程之前完成它的工作。这同样适用于解除阻塞进程。

    您可以通过人为地延迟第二个进程来提高几率,同时仍然使用后台进程:

    rm fifo; mkfifo fifo; echo a > fifo & (sleep 0.1; echo b > fifo) & cat fifo
    

    延迟时间越长,几率越大:echo a > fifo等待完成打开的块fifo,cat启动并打开fifo哪些解锁echo a,然后echo b运行。

    然而,这里的主要因素是何时cat打开 FIFO:在此之前,shell 会阻止尝试设置重定向。最终看到的输出顺序取决于写入过程被畅通的顺序。

    如果你cat先运行,你会得到不同的结果:

    rm fifo; mkfifo fifo; cat fifo & echo a > fifo & echo b > fifo
    

    这样一来,fifo写作的开放往往不会阻塞(仍然,没有保证),所以你会首先看到a比第一次设置更高的频率。您还将看到运行cat前完成,即仅输出。echo ba

    • 46
  2. Gilles 'SO- stop being evil'
    2022-01-07T05:40:54+08:002022-01-07T05:40:54+08:00

    管道是先进先出的。你的问题是你误解了“in”何时发生。“in”事件是写,而不是打开。

    删除无用的标点符号,您的代码是:

    echo a > fifo & echo b > fifo &
    

    echo a > fifo这会并行运行命令echo b > fifo。无论谁先进来,都会先出去,但对于谁先进来,这是一场大致相等的竞赛。

    如果你想a先被阅读b,你必须安排先写b。这意味着您必须等到echo a > fifo完成后才能开始echo b > fifo。

    { echo a > fifo; echo b > fifo; } & cat fifo
    

    如果你想进一步挖掘,你需要区分底层发生的基本操作。echo a > fifo结合了三个操作:

    1. 开放fifo写作。
    2. 将两个字符(a和一个换行符)写入文件。
    3. 关闭文件。

    您可以安排这些操作在不同时间发生:

    (
        exec >fifo     # 1. open
        sleep 1
        echo a         # 2. write
        sleep 1
    )                  # 3. close
    

    同样,cat foo结合了打开、读取和关闭操作。您可以将它们分开:

    (
        exec <fifo     # 1. open
        sleep 1
        read line      # 2. read
        echo $line
        sleep 1
    )                  # 3. close
    

    (read内置的 shell 实际上可能会进行多个read系统调用,但现在这并不重要。)

    Fifos 实际上并不完全是管道。它们更像是潜在的管道。fifo是一个目录条目,当进程打开fifo进行读取时,会创建一个管道对象。如果进程在不存在管道时打开 fifo 进行写入,则open调用会阻塞,直到创建管道为止。此外,如果进程打开fifo进行读取,此操作也会阻塞,直到进程打开fifo进行写入(除非读取器以非阻塞模式打开管道,这在shell中不方便)。因此,命名管道上的第一个开放读取和第一个开放写入将同时返回。

    这是一个将这些知识付诸实践的 shell 脚本。

    #!/bin/sh
    tick () { sleep 0.1; echo tick; echo 0.1; }
    mkfifo fifo
    {
        exec <fifo; echo >&2 opened for reading;
        echo a; echo >&2 wrote a
    } & writer=$!
    tick
    {
        exec >fifo; echo >&2 opened for writing;
        exec cat >&2;
    } & reader=$!
    wait $writer
    kill $reader
    rm fifo
    

    注意两个开口是如何同时发生的(尽可能接近我们可以观察到的)。写只能在那之后发生。

    注意:上面的脚本中实际上存在竞争条件——但它与管道无关。这些echo >&2命令正在争先恐后cat >&2地写入终端,因此您可能会a从cat之前看到opening for writing和wrote a. 如果您想更精确地查看时间,您可以跟踪系统调用。例如,在 Linux 下:

    mkfifo fifo
    strace -f -P fifo sh -c '…'
    

    现在,如果您放置两个写入器,两个写入器都会在开始步骤阻塞,直到读取器到达。谁先开始调用并不重要open:管道对于数据是先进先出的,而不是对于打开的尝试。谁先写才是最重要的。这是一个试验这个的脚本。

    #!/bin/sh
    mkfifo fifo
    {
        exec >fifo; echo >&2 opened for writing a
        sleep $1
        echo a; echo >&2 wrote a
    } & writer_a=$!
    {
        exec >fifo; echo >&2 opened for writing b
        sleep $2
        echo b; echo >&2 wrote b
    } & writer_b=$!
    sleep 0.2
    cat fifo & reader=$!
    wait $writer_a
    wait $writer_b
    kill $reader
    rm fifo
    

    使用两个参数调用脚本:reader a 的等待时间和 writer b 的等待时间。阅读器在 0.2 秒后上线。如果两个等待时间都小于 0.2 秒,那么两个写入器都会在写入器上线后立即尝试写入,这是一场竞赛。另一方面,如果等待时间大于 0.2,则先到者先得到输出。

    $ ./c 0.1 0.1
    # Order of "opened for writing": random
    # Order of "a"/"b": random
    # Order of "wrote": random, might not match a/b due to echo racing against each other
    $ ./c 0.3 0.4
    # Order of "opened for writing": random
    # Order of "wrote": a then b
    # Order of "a"/"b": a then b
    
    • 32

相关问题

  • 有没有办法让 ls 只显示某些目录的隐藏文件?

  • 使用键盘快捷键启动/停止 systemd 服务 [关闭]

  • 需要一些系统调用

  • astyle 不会更改源文件格式

  • 通过标签将根文件系统传递给linux内核

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    模块 i915 可能缺少固件 /lib/firmware/i915/*

    • 3 个回答
  • Marko Smith

    无法获取 jessie backports 存储库

    • 4 个回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    user12345 无法获取 jessie backports 存储库 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl 为什么大多数 systemd 示例都包含 WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve