我实际上有一段时间遇到同样的问题,特别是因为链接静态运行时(/ MT)。我从这里和那里得到了一些零碎的东西,并将其包装在一个简单易用的 RAII 对象中(显然这不在任何标题中,因为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 */
}