如何在不阻塞的情况下使用 C++ future 读取标准输入?

How to read stdin without blocking using C++ future?

测试程序 StdoutWriter 写入一些文本 ({"id":0,"cmd":1} ) 在 1 秒内输出到标准输出,然后在 5 秒后再次输出,然后等待 10 秒并退出。我已经 运行 这个程序本身并验证了时间和输出是正确的。

另一个测试程序StdinReader(下面给出的代码),使用未来读取标准输入并打印每一行。 StdinReader 未按预期工作。

我从终端启动这两个程序:

./StdoutWriter | ./StdinReader

我看到的问题是,在 StdoutWriter 退出之前,每一行的读取似乎都在阻塞。

观察到的错误输出看起来像:

waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
you wrote {"id":0,"cmd":1}
waiting...
you wrote {"id":0,"cmd":1}
waiting...
you wrote 
waiting...
you wrote 
waiting...
you wrote 

正确的 输出应该如下所示:

waiting...
waiting...
you wrote {"id":0,"cmd":1}
waiting...
waiting...
waiting...
waiting...
waiting...
you wrote {"id":0,"cmd":1}
waiting...
waiting...
waiting...
waiting...
you wrote 
waiting...
you wrote 
waiting...
you wrote

这是我从以下位置获得的 StdinReader 代码:https://gist.github.com/vmrob/ff20420a20c59b5a98a1

#include <iostream>
#include <chrono>
#include <future>
#include <string>

std::string GetLineFromCin() {
    std::string line;
    std::getline(std::cin, line);
    return line;
}

int main() {

    auto future = std::async(std::launch::async, GetLineFromCin);

    while (true) {
        if (future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
            auto line = future.get();

            // Set a new line. Subtle race condition between the previous line
            // and this. Some lines could be missed. To aleviate, you need an
            // io-only thread. I'll give an example of that as well.
            future = std::async(std::launch::async, GetLineFromCin);

            std::cout << "you wrote " << line << std::endl;
        }

        std::cout << "waiting..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

您的 StdoutWriter 程序可能实际上并没有像您认为的那样写入 stdout。如果您在写入后显式刷新输出流,那么您的 StdinReader 可能会按预期工作。

为了提高性能,大多数 I/O 库在您写入输出流时会进行一定程度的缓冲。这意味着当您写入输出流时,库通常只会将数据保留在内部,而不是直接将其写入其底层 OS 文件描述符。这避免了为每次写入支付系统调用的代价。

缓冲级别通常会因您要写入的 file/device 类型而异:

  • 写入终端时,输出通常是行缓冲的。这意味着每次您写入换行符时,库都会刷新其内部缓冲区并将输出写入底层文件描述符。

  • 写入管道或普通文件时,输出通常是完全缓冲的。这意味着库只会在缓冲区填满或您明确告诉它时将数据从其内部缓冲区刷新到底层文件描述符。