Reputation: 1240
I have an application, which reads data from standard input using getline() in a thread. I want to close the application from the main thread, while getline still block the other thread. How can this be achieved?
I don't want to force the users to have to press ctrl-Z to close stdin and the application.
I have tried so far with my complier settings (RuntimeLibrary=/MT) on Windows 8.1 64bit, v120 platform toolset:
* Update *
* Update 2: Solution *
Example code showing the problem:
#include <iostream>
#include <thread>
#include <string>
#include <chrono>
int main(int argc, char *argv[])
{
bool stop = false;
std::thread *t = new std::thread([&]{
std::string line;
while (!stop && std::getline(std::cin, line, '\n')) {
std::cout << line;
}
});
std::this_thread::sleep_for(std::chrono::seconds(1));
stop = true;
// how to stop thread or make getline to return here?
return 0;
}
Upvotes: 6
Views: 1881
Reputation: 655
I actually had this same problem for a while, specifically because of linking the static runtime (/MT). I got some bits and pieces from here an there, and wrapped it in a simple-to-use RAII object that does it for me (obviously this is not in any header because of Windows.h
):
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#else
#include <pthread.h>
#endif
struct forcefully_stop_thread_on_destruction
{
forcefully_stop_thread_on_destruction(std::thread&& thread, bool isBlockedByStdin) :
thread_(std::move(thread)),
isBlockedByStdin_(isBlockedByStdin)
{}
~forcefully_stop_thread_on_destruction()
{
#ifdef _WIN32
// Main issue on Windows is where we link the static runtime (/MT) which locks the stdin file,
// so it doesn't matter if we read stdin on background thread, it still deadlocks the process on exit & even terminate.
if (isBlockedByStdin_)
{
// On windows, if a console is attached, write to stdin so that std::getline(..) unblocks, and thread bails out naturally.
CONSOLE_SCREEN_BUFFER_INFO csbi;
const bool hasConsole = ::GetConsoleScreenBufferInfo(::GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
if (hasConsole)
{
DWORD dwTmp;
INPUT_RECORD ir[2];
ir[0].EventType = KEY_EVENT;
ir[0].Event.KeyEvent.bKeyDown = TRUE;
ir[0].Event.KeyEvent.dwControlKeyState = 0;
ir[0].Event.KeyEvent.uChar.UnicodeChar = VK_RETURN;
ir[0].Event.KeyEvent.wRepeatCount = 1;
ir[0].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
ir[0].Event.KeyEvent.wVirtualScanCode = ::MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC);
ir[1] = ir[0];
ir[1].Event.KeyEvent.bKeyDown = FALSE;
::WriteConsoleInput(::GetStdHandle(STD_INPUT_HANDLE), ir, 2, &dwTmp);
// Wait for blocking read to release and thread finish execution.
thread_.join();
}
// No console = no reliable way to unblock stdin
else
{
// WE ARE GOING NUCLEAR AT THIS POINT
// No console, so we can't release blocking stdin read: Kill whole process. Sigh.
struct terminate_process
{
~terminate_process()
{
TerminateProcess(GetCurrentProcess(), 0);
}
};
// Instantiate in "static storage" so that termination happens as late as possible (after main() returns)
static terminate_process nuclear;
// Don't wait for blocking read to release
thread_.detach();
}
}
else
{
thread_.join();
}
#else
// On unix, forcefully terminate thread.
if (isBlockedByStdin_)
{
pthread_cancel(thread_.native_handle());
}
// Wait for blocking read to release and thread finish execution.
thread_.join();
#endif
}
private:
std::thread thread_;
bool isBlockedByStdin_;
};
auto thread = std::thread([buff = inputStream.rdbuf()](){
std::string input;
std::istream inputStream(buff);
while (true)
{
std::getline(inputStream, input);
// Use input
}
});
// `inputStream` can be any stream, so verify it's stdin since that's the problem.
const auto isBlockedByStdin = inputStream.rdbuf() == std::cin.rdbuf();
auto handleProblems = forcefully_stop_thread_on_destruction(std::move(thread), isBlockedByStdin);
// Hold on to `handleProblems` until done polling stdin.
if(windows && hasConsole)
{
/* Write to console to unblock stdin */
}
else if(windows && !hasConsole)
{
/* Terminate process with exit code 0 after main() has exit (before hitting deadlock) */
}
else
{
/* Assume "Unix" & call pthread_cancel */
}
Upvotes: 0
Reputation: 1240
writeConsoleInput() can make std::getline return from blocking read, so it can solve the problem even when /MT compiler option used.
#include <Windows.h>
#include <iostream>
#include <thread>
#include <string>
#include <chrono>
#include <atomic>
int main(int argc, char *argv[])
{
std::atomic_bool stop;
stop = false;
std::thread t([&]{
std::string line;
while (!stop.load() && std::getline(std::cin, line, '\n')) {
std::cout << line;
}
});
std::this_thread::sleep_for(std::chrono::seconds(1));
stop = true;
DWORD dwTmp;
INPUT_RECORD ir[2];
ir[0].EventType = KEY_EVENT;
ir[0].Event.KeyEvent.bKeyDown = TRUE;
ir[0].Event.KeyEvent.dwControlKeyState = 0;
ir[0].Event.KeyEvent.uChar.UnicodeChar = VK_RETURN;
ir[0].Event.KeyEvent.wRepeatCount = 1;
ir[0].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
ir[0].Event.KeyEvent.wVirtualScanCode = MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC);
ir[1] = ir[0];
ir[1].Event.KeyEvent.bKeyDown = FALSE;
WriteConsoleInput(GetStdHandle(STD_INPUT_HANDLE), ir, 2, &dwTmp);
t.join();
return 0;
}
Upvotes: 2
Reputation: 36348
This works for me, although it's a bit dodgy:
#include <Windows.h>
#include <iostream>
#include <thread>
#include <string>
#include <chrono>
#include <atomic>
int main(int argc, char *argv[])
{
std::atomic_bool stop;
stop = false;
std::thread t([&]{
std::string line;
while (!stop.load() && std::getline(std::cin, line, '\n')) {
std::cout << line;
}
});
std::this_thread::sleep_for(std::chrono::seconds(1));
stop = true;
CloseHandle(GetStdHandle(STD_INPUT_HANDLE));
t.join();
return 0;
}
Upvotes: 0
Reputation: 26536
this code is multi-threaded flawd. first of all, why create a new thread on the heap? just declare it on the stack and call std::thread::detach
.
second, who promised you that stop
in this context will work? it is more than possible that the processor caches this boolean and never looks at the real one (if not partially optimize it away, or other compiling tricks..).
you need to make it atomic:
int main(int argc, char *argv[])
{
std::atomic_bool stop;
stop = false;
std::thread t([&]{
std::string line;
while (!stop.load() && std::getline(std::cin, line, '\n')) {
std::cout << line;
}
});
t.detach();
stop = true;
}
compiled with visual studio 2013 on windows 7 and works as expected.
Upvotes: 0
Reputation: 36348
If nothing else works, there's always the nuclear option:
TerminateProcess(GetCurrentProcess(), 0);
Just make sure you've flushed any of the runtime buffers you care about.
Upvotes: 0
Reputation:
Just detach the thread:
#include <iostream>
#include <thread>
#include <chrono>
bool stop = false;
int main(int argc, char *argv[])
{
std::thread t([]{
bool stop = false;
std::string line;
while (!stop && std::getline(std::cin, line, '\n')) {
std::cout << line;
}
});
std::this_thread::sleep_for(std::chrono::seconds(1));
stop = true;
// Without detach: g++: terminate called without an active exception
t.detach();
return 0;
}
Cleaner ways are
Upvotes: 0
Reputation: 13013
There is no standard and even cross-platform solution to interrupt std:cin
or std::thread
. You will need to use OS-specific APIs in both cases. You could retrieve OS-specific handle for a thread with std::thread::native_handle()
As a quick and dirty hack you could just detach thread. But be aware of this and that.
int main(int argc, char *argv[]) {
std::thread t([&] {
std::string line;
while (std::getline(std::cin, line, '\n')) {
std::cout << line;
}
});
t.detach();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
Also:
No need to allocate thread on heap:
std::thread t([]{
});
return 0;
is unnecessary in C++stop = true;
will trigger compilation error, as stop
is not declared in this scopeUpvotes: 0