在标准输入阻塞 windows 时退出应用程序
Exit application while stdin blocking on windows
我有一个应用程序,它在线程中使用 getline() 从标准输入读取数据。我想从主线程关闭应用程序,而 getline 仍然阻塞另一个线程。如何实现?
我不想强迫用户必须按 ctrl-Z 来关闭标准输入和应用程序。
到目前为止,我已经尝试在 Windows 8.1 64 位 v120 平台工具集上使用我的编译器设置 (RuntimeLibrary=/MT):
- freopen stdin,但被内部锁阻塞
- 销毁调用 abort() 的线程
- 回放一个 Eof,行尾到 std::cin,这也被阻塞了
*更新*
- detach() 不起作用,exit() 被锁阻塞
- winapi TerminatThread() 调用 abort()
- winapi CloseHandle(GetStdHandle(STD_INPUT_HANDLE)) 挂起
- 调用 TerminateProcess() - 有效,但我想正常退出
* 更新 2:解决方案 *
- WriteConsoleInput() 可以使 std::getline() return 避免阻塞读取。这适用于任何 msvc 运行时库。有关工作解决方案的代码,请参阅已接受的答案。
显示问题的示例代码:
#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;
}
只需分离线程:
#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;
}
更简洁的方法是
- 如果 stdin 正在获取用户输入,请在线程中正确退出(不要突然终止交互式输入)
- 从标准输入(取决于系统)的非阻塞读取
- 设置管道
- 使用套接字
中断 std:cin
或 std::thread
没有标准甚至跨平台的解决方案。在这两种情况下,您都需要使用 OS 特定的 API。您可以使用 std::thread::native_handle()
检索线程的 OS 特定句柄
作为一个快速而肮脏的 hack,你可以 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));
}
还有:
无需在堆上分配线程:
std::thread t([]{
});
return 0;
在 C++ 中是不必要的
stop = true;
将触发编译错误,因为 stop
未在此范围内声明
- 如果您打算以这种方式共享布尔标志,您将有一个典型的 race condition and thus UB
- 可能最接近 "standard" 或 "cross-platform" 的非阻塞输入解决方案可能是 ncurses(如在 *nix 上,pdcurses 在 Windows 上)
如果没有别的办法,总有核选项:
TerminateProcess(GetCurrentProcess(), 0);
只要确保您已经刷新了您关心的任何运行时缓冲区。
此代码存在多线程缺陷。首先,为什么要在堆上创建一个新线程?只需在堆栈上声明它并调用 std::thread::detach
。
第二,谁向你保证 stop
在这种情况下会起作用?处理器很可能会缓存这个布尔值并且从不查看真实的布尔值(如果不是部分优化它,或者其他编译技巧......)。
你需要让它成为原子:
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;
}
在 windows 7 上使用 visual studio 2013 编译并按预期工作。
这对我有用,虽然有点不可靠:
#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;
}
writeConsoleInput()可以让std::getlinereturn不阻塞读取,所以即使使用/MT编译器选项也能解决问题。
#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;
}
我实际上有一段时间遇到了同样的问题,特别是因为链接静态运行时 (/MT)。我从这里那里得到了一些零碎的东西,然后把它包裹在一个 simple-to-use RAII object 中,它为我做的(显然这不在任何 header 因为 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 */
}
我有一个应用程序,它在线程中使用 getline() 从标准输入读取数据。我想从主线程关闭应用程序,而 getline 仍然阻塞另一个线程。如何实现?
我不想强迫用户必须按 ctrl-Z 来关闭标准输入和应用程序。
到目前为止,我已经尝试在 Windows 8.1 64 位 v120 平台工具集上使用我的编译器设置 (RuntimeLibrary=/MT):
- freopen stdin,但被内部锁阻塞
- 销毁调用 abort() 的线程
- 回放一个 Eof,行尾到 std::cin,这也被阻塞了
*更新*
- detach() 不起作用,exit() 被锁阻塞
- winapi TerminatThread() 调用 abort()
- winapi CloseHandle(GetStdHandle(STD_INPUT_HANDLE)) 挂起
- 调用 TerminateProcess() - 有效,但我想正常退出
* 更新 2:解决方案 *
- WriteConsoleInput() 可以使 std::getline() return 避免阻塞读取。这适用于任何 msvc 运行时库。有关工作解决方案的代码,请参阅已接受的答案。
显示问题的示例代码:
#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;
}
只需分离线程:
#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;
}
更简洁的方法是
- 如果 stdin 正在获取用户输入,请在线程中正确退出(不要突然终止交互式输入)
- 从标准输入(取决于系统)的非阻塞读取
- 设置管道
- 使用套接字
中断 std:cin
或 std::thread
没有标准甚至跨平台的解决方案。在这两种情况下,您都需要使用 OS 特定的 API。您可以使用 std::thread::native_handle()
作为一个快速而肮脏的 hack,你可以 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));
}
还有:
无需在堆上分配线程:
std::thread t([]{ });
return 0;
在 C++ 中是不必要的stop = true;
将触发编译错误,因为stop
未在此范围内声明- 如果您打算以这种方式共享布尔标志,您将有一个典型的 race condition and thus UB
- 可能最接近 "standard" 或 "cross-platform" 的非阻塞输入解决方案可能是 ncurses(如在 *nix 上,pdcurses 在 Windows 上)
如果没有别的办法,总有核选项:
TerminateProcess(GetCurrentProcess(), 0);
只要确保您已经刷新了您关心的任何运行时缓冲区。
此代码存在多线程缺陷。首先,为什么要在堆上创建一个新线程?只需在堆栈上声明它并调用 std::thread::detach
。
第二,谁向你保证 stop
在这种情况下会起作用?处理器很可能会缓存这个布尔值并且从不查看真实的布尔值(如果不是部分优化它,或者其他编译技巧......)。
你需要让它成为原子:
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;
}
在 windows 7 上使用 visual studio 2013 编译并按预期工作。
这对我有用,虽然有点不可靠:
#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;
}
writeConsoleInput()可以让std::getlinereturn不阻塞读取,所以即使使用/MT编译器选项也能解决问题。
#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;
}
我实际上有一段时间遇到了同样的问题,特别是因为链接静态运行时 (/MT)。我从这里那里得到了一些零碎的东西,然后把它包裹在一个 simple-to-use RAII object 中,它为我做的(显然这不在任何 header 因为 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 */
}