Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

non-blocking std::getline, exit if no input

Tags:

c++

istream

Currently I have a program that reads from the standard input, occasionally the program needs to just keep running if no input is made, usually this is a test script there is no 'enter' so to speak.

program -v1 -v2 -v3 <input >output

v1 - v3 are command line arguments respectively

Basically the program spits out the command line arguments and their respective meaning to the program if no 'input' is given and then should exit.

However at the moment if give it an empty test file or just run without pressing enter after running it blocks on the std::getline I use to input the commands.

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

where foo is a string.

How do I get it to just run through and do stuff at least once then exit in the event of no input? In the event of input the do stuff occurs once for every line in standard input.

Would a switch to a do-while loop, with a check pre loop as to whether it's got any input, work?

Something like

if cin empty
set flag

do
 {do stuff
 check flag}
while(getline)

or is non-blocking io not possible in c++?

This question seems to be rehashed over and over but I couldn't find a definitive answer or even an answer that was platform agnostic(this program is academic in nature, coded on windows and tested on unix).

like image 524
SGE Avatar asked May 16 '13 16:05

SGE


People also ask

Does Getline return EOF?

Thus, when the pointer is at the last character, it will return EOF.

Does Getline include null-terminated?

The getline() function reads an entire line from stream , storing the address of the buffer containing the text into *lineptr . The buffer is null-terminated and includes the newline character, if one is found.

Does Getline ignore newline?

In the line “cin >> c” we input the letter 'Z' into variable c. However, the newline when we hit the carriage return is left in the input stream. If we use another cin, this newline is considered whitespace and is ignored. However, if we use getline, this is not ignored.

Does Getline ignore whitespace C++?

The getline() function in C++ is used to read a string or a line from the input stream. The getline() function does not ignore leading white space characters.


1 Answers

Using std::cin asynchronously might be the only way to make this work, as iostream is not designed to have non-blocking behavior. Here is an example:

Async Example 1 Async Example 2 (printing a space out every 1/10th of a second while accepting CLI input at the same time)

The code is commented so it should be easy to understand. It's a thread-safe class that lets you asynchronously get a line using std::cin.

Very easy to use for asynchronous CLI getline purposes, with 0% CPU usage on my computer. It 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.

#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");
}
like image 184
Andrew Avatar answered Oct 05 '22 10:10

Andrew