【问题标题】:non-blocking std::getline, exit if no input非阻塞 std::getline,如果没有输入则退出
【发布时间】:2013-05-11 15:17:09
【问题描述】:

目前我有一个从标准输入读取的程序,有时如果没有输入,程序需要继续运行,通常这是一个测试脚本,可以说没有“输入”。

program -v1 -v2 -v3 <input >output

v1 - v3 分别是命令行参数

如果没有给出“输入”,基本上程序会向程序吐出命令行参数及其各自的含义,然后应该退出。

但是,目前如果给它一个空的测试文件,或者在我用来输入命令的 std::getline 上运行它后不按回车键就运行它。

while(std::getline(std::cin,foo)
{do stuff}

其中 foo 是一个字符串。

如何让它运行并至少运行一次do stuff,然后在没有输入的情况下退出?在输入事件中,do stuff 在标准输入中的每一行出现一次。

切换到 do-while 循环并检查预循环是否有任何输入是否可行?

类似

if cin empty
set flag

do
 {do stuff
 check flag}
while(getline)

或者c++中不能实现非阻塞io?

这个问题似乎一遍又一遍地重复,但我找不到明确的答案,甚至找不到与平台无关的答案(这个程序本质上是学术性的,在 windows 上编码并在 unix 上测试)。

【问题讨论】:

  • 所以你是说无论如何你都希望循环运行一次,然后如果在 getline 调用之前没有输入就退出?
  • checking data availability before calling std::getline 的可能副本 不幸的是,在标准 C++ 中可能没有可移植的方式来执行此操作。
  • 你能使用 C 中的一些低级函数吗?

标签: c++ istream


【解决方案1】:

异步使用 std::cin 可能是完成这项工作的唯一方法,因为 iostream 并非设计为具有非阻塞行为。这是一个例子:

代码已注释,因此应该易于理解。它是一个线程安全的类,可让您使用 std::cin 异步获取一行。

非常容易用于异步 CLI getline 目的,我的计算机上的 CPU 使用率为 0%。它在 Visual Studio 2015 c++ Win32 控制台调试和发布模式下的 Windows 10 上运行良好。如果它在您的操作系统或环境中不起作用,那就太糟糕了。

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <atomic>

using namespace std;

//This code works perfectly well on Windows 10 in Visual Studio 2015 c++ Win32 Console Debug and Release mode.
//If it doesn't work in your OS or environment, that's too bad; guess you'll have to fix it. :(
//You are free to use this code however you please, with one exception: no plagiarism!
//(You can include this in a much bigger project without giving any credit.)
class AsyncGetline
{
    public:
        //AsyncGetline is a class that allows for asynchronous CLI getline-style input
        //(with 0% CPU usage!), which normal iostream usage does not easily allow.
        AsyncGetline()
        {
            input = "";
            sendOverNextLine = true;
            continueGettingInput = true;

            //Start a new detached thread to call getline over and over again and retrieve new input to be processed.
            thread([&]()
            {
                //Non-synchronized string of input for the getline calls.
                string synchronousInput;
                char nextCharacter;

                //Get the asynchronous input lines.
                do
                {
                    //Start with an empty line.
                    synchronousInput = "";

                    //Process input characters one at a time asynchronously, until a new line character is reached.
                    while (continueGettingInput)
                    {
                        //See if there are any input characters available (asynchronously).
                        while (cin.peek() == EOF)
                        {
                            //Ensure that the other thread is always yielded to when necessary. Don't sleep here;
                            //only yield, in order to ensure that processing will be as responsive as possible.
                            this_thread::yield();
                        }

                        //Get the next character that is known to be available.
                        nextCharacter = cin.get();

                        //Check for new line character.
                        if (nextCharacter == '\n')
                        {
                            break;
                        }

                        //Since this character is not a new line character, add it to the synchronousInput string.
                        synchronousInput += nextCharacter;
                    }

                    //Be ready to stop retrieving input at any moment.
                    if (!continueGettingInput)
                    {
                        break;
                    }

                    //Wait until the processing thread is ready to process the next line.
                    while (continueGettingInput && !sendOverNextLine)
                    {
                        //Ensure that the other thread is always yielded to when necessary. Don't sleep here;
                        //only yield, in order to ensure that the processing will be as responsive as possible.
                        this_thread::yield();
                    }

                    //Be ready to stop retrieving input at any moment.
                    if (!continueGettingInput)
                    {
                        break;
                    }

                    //Safely send the next line of input over for usage in the processing thread.
                    inputLock.lock();
                    input = synchronousInput;
                    inputLock.unlock();

                    //Signal that although this thread will read in the next line,
                    //it will not send it over until the processing thread is ready.
                    sendOverNextLine = false;
                }
                while (continueGettingInput && input != "exit");
            }).detach();
        }

        //Stop getting asynchronous CLI input.
        ~AsyncGetline()
        {
            //Stop the getline thread.
            continueGettingInput = false;
        }

        //Get the next line of input if there is any; if not, sleep for a millisecond and return an empty string.
        string GetLine()
        {
            //See if the next line of input, if any, is ready to be processed.
            if (sendOverNextLine)
            {
                //Don't consume the CPU while waiting for input; this_thread::yield()
                //would still consume a lot of CPU, so sleep must be used.
                this_thread::sleep_for(chrono::milliseconds(1));

                return "";
            }
            else
            {
                //Retrieve the next line of input from the getline thread and store it for return.
                inputLock.lock();
                string returnInput = input;
                inputLock.unlock();

                //Also, signal to the getline thread that it can continue
                //sending over the next line of input, if available.
                sendOverNextLine = true;

                return returnInput;
            }
        }

    private:
        //Cross-thread-safe boolean to tell the getline thread to stop when AsyncGetline is deconstructed.
        atomic<bool> continueGettingInput;

        //Cross-thread-safe boolean to denote when the processing thread is ready for the next input line.
        //This exists to prevent any previous line(s) from being overwritten by new input lines without
        //using a queue by only processing further getline input when the processing thread is ready.
        atomic<bool> sendOverNextLine;

        //Mutex lock to ensure only one thread (processing vs. getline) is accessing the input string at a time.
        mutex inputLock;

        //string utilized safely by each thread due to the inputLock mutex.
        string input;
};

void main()
{
    AsyncGetline ag;
    string input;

    while (true)
    {
        //Asynchronously get the next line of input, if any. This function automagically
        //sleeps a millisecond if there is no getline input.
        input = ag.GetLine();

        //Check to see if there was any input.
        if (!input.empty())
        {
            //Print out the user's input to demonstrate it being processed.
            cout << "{" << input << "}\n";

            //Check for the exit condition.
            if (input == "exit")
            {
                break;
            }
        }

        //Print out a space character every so often to demonstrate asynchronicity.
        //cout << " ";
        //this_thread::sleep_for(chrono::milliseconds(100));
    }

    cout << "\n\n";
    system("pause");
}

【讨论】:

  • 嘿,我在这方面做了很多工作,它工作得很好,并且有非常好的 cmets。为什么要投反对票?
  • 我不确定为什么这被否决了,但这是一个赞成票!
  • 小注:continueGettingInputinputLock 等将在析构函数结束后立即成为垃圾(AKA 未定义的行为和充其量是崩溃)。您需要将其设为 shared_ptr 并在轮询线程中按值捕获它(可能将其打包在 SharedState 结构中)。
  • 这太好了,谢谢安德鲁。 helmesjo,是否可以在不使用智能指针的情况下命名线程 t 并在类析构函数中添加 t.join() 来解决这个问题? std::atomic has 没有分配任何内存,所以我假设那时不会有垃圾。
  • 我在 Linux 上对其进行了测试,它可以工作,但可能不是它的本意。特别是 cin.peek() 块,因此 this_thread.yield() 仅在 cin 已关闭时才被调用。
【解决方案2】:

您可以使用istream::readsome() 方法相当容易地创建一个非阻塞等效于 std::getline 的方法。这会读取最大缓冲区大小的可用输入,而不会阻塞。

此函数将始终立即返回,但如果流中可用,则将捕获一行。部分行存储在静态变量中,直到下一次调用。

bool getline_async(std::istream& is, std::string& str, char delim = '\n') {

    static std::string lineSoFar;
    char inChar;
    int charsRead = 0;
    bool lineRead = false;
    str = "";

    do {
        charsRead = is.readsome(&inChar, 1);
        if (charsRead == 1) {
            // if the delimiter is read then return the string so far
            if (inChar == delim) {
                str = lineSoFar;
                lineSoFar = "";
                lineRead = true;
            } else {  // otherwise add it to the string so far
                lineSoFar.append(1, inChar);
            }
        }
    } while (charsRead != 0 && !lineRead);

    return lineRead;
}

【讨论】:

  • 这实际上并没有读取任何内容(至少在从控制台输入读取时不会)。使用上面的代码,我无法在控制台中输入。
【解决方案3】:

您可以使用cin.peek 来检查是否有要阅读的内容,如果有,请致电getline。不过,本身并没有非阻塞 getline 这样的东西。

【讨论】:

  • 如果没有可用的输入,这也会阻塞。
  • cin.peek 应该阻止,因为根据标准:如果 good() 为 false,则返回:traits::eof()。否则,返回 rdbuf()->sgetc() 据我所知,应该不可能使用peek() 以解锁方式检查流。
猜你喜欢
  • 1970-01-01
  • 2020-09-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-19
相关资源
最近更新 更多