'How to define a non blocking input in C++
I'm making a multithread application in C++. In particular, a secondary thread is involved in input operations, the problem is that std::cin is a blocking instruction and this create some problems in the execution flow of my program. If main thread end its execution, secondary thread should do the same, but it's blocked on std::cin, so user has to insert something to let the program to end. It's possible to overcome this problem?
Here's the code:
#include <iostream>
#include <future>
#include <string>
#include <chrono>
#include <thread>
#include <atomic>
std::atomic<bool> interrupt(false);
std::string get_input(){
std::cout << "Insert a new command: ";
std::string cmd;
std::cin >> cmd;
return cmd;
}
void kill_main_thread(){
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
interrupt = true;
}
int main() {
std::thread new_thread (kill_main_thread); // spawn new thread that calls foo()
while(1) {
if ( interrupt ) {
return 0;
}
std::future<std::string> fut = std::async(get_input);
if ( fut.wait_for(std::chrono::seconds(500)) == std::future_status::timeout ){
std::string cmd = fut.get();
if ( cmd == "close") std::cout << "close command\n";
else if ( cmd == "open") std::cout << "open command\n";
else std::cout << "not recognized command\n";
}
}
return 0;
}
So basically I found three possible solutions for my purpose:
1.) Is it possible to realize a non blocking std::cin? 2.) Is there any interrupt or kill signal that will stop execution of the secondary thread? 3.) Is it possible to virtualize in some way std::cin? In kill_main_thread() function I tried it by using this instruction:
void kill_main_thread(){
...
...
std::istringstream iss;
std::cin.rdbuf(iss);
}
But secondary thread is still blocked on std::cin.
I also tried by using getch() function of conio.h library to create a non blocking input function to replace std::cin, but first of all my application should preferly work on different OS and secondary it create a bit of problem with managing of console because I need to change every std::cout of my program in this way
Solution 1:[1]
As Igor Tandetnik pointed out, there isn't a standard solution to this problem because console input is platform-dependent; There is however a simple workaround that involves using the preprocessor to select an approach depending on which platform you're compiling on.
Note that this isn't perfect - there are plenty of more obscure platforms that won't be detected with this - however it is easily expandable. So long as the platform has predefined macros, you can support it with this method.
#if defined(__GNUG__) || defined(__GNUC__)
#define OS_LINUX
#elif defined(_MAC)
#define OS_MAC
#elif defined(_WIN32)
#define OS_WIN
#endif
Once you have determined what operating system your code is being compiled on, you can handle things differently for each OS.
#ifdef OS_WIN
#include <conio.h>
#elif defined(OS_LINUX) || defined(OS_MAC)
#include <unistd.h>
#include <sys/socket.h>
#endif
bool stdinHasData()
{
# if defined(OS_WIN)
// this works by harnessing Windows' black magic:
return _kbhit();
# elif defined(OS_LINUX) || defined(OS_MAC)
// using a timeout of 0 so we aren't waiting:
struct timespec timeout{ 0l, 0l };
// create a file descriptor set
fd_set fds{};
// initialize the fd_set to 0
FD_ZERO(&fds);
// set the fd_set to target file descriptor 0 (STDIN)
FD_SET(0, &fds);
// pselect the number of file descriptors that are ready, since
// we're only passing in 1 file descriptor, it will return either
// a 0 if STDIN isn't ready, or a 1 if it is.
return pselect(0 + 1, &fds, nullptr, nullptr, &timeout, nullptr) == 1;
# else
// throw a compiler error
static_assert(false, "Failed to detect a supported operating system!");
# endif
}
Now we can use a single function to determine whether STDIN is ready or not.
Here's an example of a simple input loop:
#include <iostream>
// ...
int main()
{
int keyCode{ 0 };
// continue looping until the user enters a lowercase 'q'
while (keyCode != 'q')
{
if (stdinHasData()) {
keyCode = std::cin.get();
std::cout << "You pressed '" << keyCode << "'\n";
}
}
return 0;
}
To use this with multithreading, simply put it in your thread function.
Solution 2:[2]
In my code I dealt with this problem by writing a platform-independent wrapper class whose caller-visible behavior is the same for all the platforms I need to support. In particular, it provides these methods to the calling code:
Read()
, which copies up to (n) bytes of stdin-produced data into the user's supplied buffer without ever blocking -- if no bytes are available, it immediately returns 0.GetReadSelectSocket()
, which returns a socket-descriptor that can be passed toselect()
(orpoll()
orWSASelect()
or whatever multiplexing function you are using to block waiting for I/O events), with the guarantee that the socket will select()-as-ready-for-read as soon as there is more data available from stdin to read.
That's the behavior I want, and under POSIX-style OS's, it's easy enough to implement; Read()
just sets STDIN_FILENO
to non-blocking mode before trying to read()
from it, and GetReadSelectSocket()
just returns STDIN_FILENO
.
Achieving that same behavior under Windows is much more difficult; it appears the Win32 API creators never implemented support for non-blocking reads from stdin. So for Windows I have to fake it by having the Windows implementation of my class do the following:
- Create a socket-pair such that any data written to one socket appears available-for-reading on the other socket
- Spawn a thread that does blocking calls to
ReadFile(GetStdHandle(STD_INPUT_HANDLE))
in a loop, and writes the read data to one socket of the socket-pair - Implement the
GetReadSelectSocket()
method to return the other socket of the socket-pair, and implement theRead()
method to read from that other socket.
With that completed, a Windows program can now "read from stdin" using the same semantics as reading from any other network/TCP socket, because it is in fact reading from a network/TCP socket. This isn't super-efficient, but it does have the advantage of allowing me to write programs that read from stdin under both Windows and (every other OS) without having to do any #ifdef
-ing or other Windows-specific behavior-modifications in the vast majority of my codebase.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |