6

我有一个应用程序,它在线程中使用 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() 从阻塞读取中返回。这适用于任何 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;
}
4

7 回答 7

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;
}
于 2015-07-30T10:55:33.207 回答
0

我实际上有一段时间遇到同样的问题,特别是因为链接静态运行时(/ 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 */ 
}
于 2020-10-14T16:40:23.003 回答
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;
}

更清洁的方法是

  • 如果标准输入正在获取用户输入,请在线程中正确退出(不要突然终止交互式输入)
  • 来自标准输入的非阻塞读取(取决于系统)
  • 设置管道
  • 使用套接字
于 2015-07-27T16:07:05.013 回答
0

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

作为一个快速而肮脏的黑客,你可以分离线程。但要注意这个那个

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未在此范围内声明
  • 如果您打算以这种方式共享布尔标志,您将有一个典型的竞争条件,因此UB
  • 可能最接近非阻塞输入的“标准”或“跨平台”解决方案可能是 ncurses(如在 *nix 上和Windows 上的pdcurses
于 2015-07-27T16:07:49.527 回答
0

如果没有其他办法,总有核选项:

TerminateProcess(GetCurrentProcess(), 0);

只要确保你已经刷新了你关心的任何运行时缓冲区。

于 2015-07-27T21:29:57.953 回答
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 编译并按预期工作。

于 2015-07-28T08:36:58.713 回答
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;
}
于 2015-07-28T21:48:16.197 回答