Как читать stdin без блокировки, используя C ++ в будущем? - PullRequest
0 голосов
/ 23 апреля 2020

Тестовая программа StdoutWriter записывает некоторый текст ( {"id": 0, "cmd": 1} ) в стандартный вывод в течение 1 секунды, а затем снова через 5 секунд, а затем ждет 10 секунд и выходит. Я запустил эту программу сам и проверил правильность времени и результатов.

Другая тестовая программа StdinReader (код приведен ниже), использующий в будущем чтение stdin и печать каждой строки. 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));
    }
}

1 Ответ

1 голос
/ 23 апреля 2020

Ваша программа StdoutWriter, скорее всего, на самом деле не пишет в стандартный вывод, как вы думаете. Если вы явно передаете sh свой выходной поток после записи, тогда ваш StdinReader, скорее всего, будет работать так, как вы ожидаете.

Для повышения производительности большинство библиотек ввода / вывода выполняют некоторую буферизацию при записи в выходной поток. Это означает, что при записи в выходной поток библиотека, как правило, просто хранит данные внутри, а не записывает их непосредственно в свой дескриптор файла ОС. Это позволяет избежать платы за выполнение системных вызовов для каждой записи.

Уровень буферизации часто зависит от типа файла / устройства, на которое вы записываете:

  • При записи в терминал вывод очень часто буферизуется. Это означает, что библиотека будет грипповать sh свои внутренние буферы и записывать выходные данные в базовый дескриптор файла каждый раз, когда вы пишете символ новой строки.

  • При записи в каналы или обычные файлы вывод как правило, полностью буферизованный, хотя. Это означает, что библиотека будет отправлять sh данные только из своего внутреннего буфера в базовый файловый дескриптор, когда буфер заполнится, или вы явно скажете это.

...