【问题标题】:Why does select on a named pipe with no writers block indefinitely?为什么在没有作者的命名管道上选择会无限期地阻塞?
【发布时间】:2017-06-19 04:53:55
【问题描述】:

我在 read_fds 中使用单个命名管道 fd 调用 select。此命名管道没有写入器,仅在非阻塞、只读模式下打开。我希望选择返回的命名管道 fd 标记为准备好读取,并且尝试从管道中读取返回 0:

从手册页上阅读:

尝试从空管道或 FIFO 中读取时:

  • 如果没有进程打开管道进行写入,read() 应返回 0 以 > 指示文件结束。

但是,无限期地只选择块。为什么会这样?

#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

#include <stdexcept>
#include <thread>
#include <iostream>

int main()
{
    char buf[4096];

    // Create a named pipe
    auto err = mkfifo("/tmp/whatever",0666);
    if(err) {
        throw std::runtime_error(
                    std::string("Failed to create fifo ")+
                    strerror(errno));
    }

    std::thread reader_thread(
                [&](){
        auto fd = open("/tmp/whatever",O_RDONLY|O_NONBLOCK);
        if(fd < 0) {
            throw std::runtime_error("Failed to open fifo");
        }

        fd_set fds;
        while(1) {
            FD_ZERO(&fds);
            FD_SET(fd,&fds);
            std::cerr << "calling select" << std::endl;
            auto retval = select(fd+1,&fds,nullptr,nullptr,nullptr);
            if(retval < 0) {
                std::runtime_error("Failed to call select");
            }

            if(FD_ISSET(fd,&fds)) {
                auto read_bytes = read(fd,buf,4096);
                std::cerr << "read " << read_bytes << std::endl;
                if(read_bytes==0) {
                    break;
                }
            }
        }

        close(fd);
    });

    reader_thread.join();

    return 0;
}

【问题讨论】:

  • 不要垃圾标签! C 不是 C++ 不是 C!
  • 如果没有什么可读的,为什么select要返回??
  • @Olaf> 因为 select 的手册页明确指出 “文件描述符在文件结束时也已准备就绪” 而 fifo 手册页还说 “如果引用管道写入端的所有文件描述符都已关闭,则尝试从管道读取 (2) 将看到文件结束”。这实际上是一个很好的问题。
  • 我没有看到一个 openend 管道在 EOF 时从未打开写。这只会为双方开放之间的竞争条件敞开大门!
  • @Stargateur> 因为 select 的手册页说 select 观察到的确切条件是 “查看读取 (2) 是否不会阻塞”

标签: c++ linux select posix nonblocking


【解决方案1】:

来自select 的 POSIX 文档:

当对带有 O_NONBLOCK 清除的输入函数的调用不会阻塞时,无论该函数是否会成功传输数据,都应认为描述符已准备好读取。 (该函数可能返回数据、文件结束指示或指示它被阻塞的错误以外的错误,并且在这些情况下,描述符应被视为已准备好读取。

...

如果选定的描述符都没有为请求的操作准备好,则 pselect() 或 select() 函数应阻塞,直到至少有一个请求的操作准备好,直到超时发生,或直到被信号打断。

来自pipe(7) 联机帮助页(这是 FIFO 的底层对象):

如果引用管道写入端的所有文件描述符都已关闭,则尝试从管道读取(2)将看到文件结束(读取(2)将返回 0)。

注意现在完成时的用法!这意味着 FIFO 必须在两侧打开首先,在写入端(对于您的应用程序)关闭以生成EOF 条件。

那么,除非fifo最终被作者关闭,否则select为什么要返回呢? (fifo-)文件本身的设置是无关紧要的:当使用最有效的方法一次读取多个字节时,它会在两边打开之间引入竞争条件。这是例如的正常方式命令管道:启动读取器进程,然后启动写入器(使用命名管道时通常是完全不相关的程序)。

如果您希望 select 提前返回,请使用 timeout 参数。但通常情况下,使用可以由信号终止的单独线程(有关更多信息,请参阅select 手册页)。

附带说明:Linux/POSIX 的一个优点是,使用 FIFO、文件或麦克风驱动程序并不重要。

【讨论】:

  • 大坝,我刚刚发现了! pubs.opengroup.org/onlinepubs/9699919799
  • @Stargateur:不确定你的意思;我只是得到一个全球页面。以上信息来自 Linux 联机帮助页 (3)。我故意不使用特定于 Linux 的命令(交叉检查没有发现相关的差异)。我希望一切都是正确的,我只是看了一眼手册页;这通常不是我工作的领域。
  • 哦,我系统上的手册页没有显示...但你是对的linux.die.net/man/3/select。我并不是说我在您回答的同时找到了这个文档(回答了这个问题)。所以我觉得我什么都不搜索:p。
  • @Stargateur:你的发行版有问题,或者忘记安装man-all(或all-man或类似)包:-)
  • 是的,原因是有道理的,尤其是否则除非通过繁忙的循环,否则无法等待第一个写入者。奇怪的是文档没有更明确,我希望您的答案在搜索引擎结果中找到一个好位置。
猜你喜欢
  • 1970-01-01
  • 2020-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多