在stdin阻塞Windows

时间:2015-07-27 15:55:55

标签: windows multithreading c++11 stdin msvc12

我有一个应用程序,它使用线程中的getline()从标准输入读取数据。我想从主线程关闭应用程序,而getline仍然阻止其他线程。如何实现这一目标?

我不想强迫用户按ctrl-Z来关闭stdin和应用程序。

到目前为止,我已尝试使用Windows 8.1 64bit,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()从阻塞读取返回。这适用于任何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;
}

7 个答案:

答案 0 :(得分:2)

writeConsoleInput()可以使std :: getline从阻塞读取返回,因此即使使用/ 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;
}

答案 1 :(得分:0)

只需分离主题:

import sys ; print(sys.path)

更清洁的方式

  • 如果stdin正在获取用户输入,请在线程中正确退出(不要终止交互式输入)
  • stdin的非阻塞读取(取决于系统)
  • 设置管道
  • 使用套接字

答案 2 :(得分:0)

没有标准甚至是跨平台的解决方案来中断std:cinstd::thread。在这两种情况下,您都需要使用特定于操作系统的API。您可以为std::thread::native_handle()

的线程检索特定于操作系统的句柄

作为一个快速而肮脏的黑客,你可以detach线程。但请注意thisthat

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,因此UB
  • 对于非阻塞输入,可能最接近“标准”或“跨平台”解决方案可能是ncurses(在* nix上,在Windows上为pdcurses

答案 3 :(得分:0)

如果没有其他方法可行,那么核选项总是存在:

TerminateProcess(GetCurrentProcess(), 0);

请确保您已刷新了您关心的任何运行时缓冲区。

答案 4 :(得分: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;
}

使用Visual Studio 2013在Windows 7上编译并按预期工作。

答案 5 :(得分:0)

这对我有用,虽然它有点狡猾:

#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;
}

答案 6 :(得分:0)

我实际上有一段时间遇到了同样的问题,特别是因为链接了静态运行时(/ MT)。我从这里的 here 中得到了一些零碎的信息,并将其包装在一个对我有用的简单使用的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 */ 
}