如何向std::cin外部写入换行,终止阻塞的getline以退出线程?
解决std::getline阻塞导致线程无法正常退出的问题
问题背景
实现命令行Shell时,子线程通过std::getline读取std::cin,主线程计划在8秒后终止该子线程,但std::getline的阻塞状态导致线程无法正常退出,必须手动按下ENTER才能完成退出流程。
尝试过的无效方案
- 向
std::cin写入换行(包括操作rdbuf、调用putback等方法) - 注入自定义
streambuf替换cin的缓冲区 - 重定向
ifstream到cin
以上方案均无法唤醒阻塞的std::getline,问题未解决。
最终解决方案
实现非阻塞异步输入的AsyncGetline类,通过平台相关的键盘检测接口实现无阻塞的输入读取,同时保证线程可以随时响应终止信号。该类通过周期性检测键盘输入状态,避免了std::getline的永久阻塞,让线程能够正常退出。
完整代码
// test.cpp #include <iostream> // std::cin, std::cout #include <ostream> // std::ostringstream #include <fstream> // std::ifstream #include <chrono> #include <thread> // std::thread #include <atomic> // std::atomic #include <sstream> // std::istringstream #include <csignal> #include <mutex> // std::mutex // 平台兼容的键盘输入头文件 //#include <conio.h> // Windows 平台直接使用 #include "linux_conio/linux_conio.h" // Linux 平台需克隆模拟实现 // 编译命令: // Windows: g++ -g -std=c++11 test.cpp -o test.exe -pthread // Linux: g++ -g -std=c++11 linux_conio/linux_conio.cpp test.cpp -o test.exe -pthread class AsyncGetline { public: // 异步读取命令行输入的类,避免阻塞导致线程无法退出 AsyncGetline() { input = ""; sendOverNextLine = true; continueGettingInput = true; tj = std::thread([&]() { in_thread = true; std::string synchronousInput; char nextCharacter; int did_kbhit = 0; do { synchronousInput = ""; while (continueGettingInput.load()) { while( continueGettingInput.load() and (!(did_kbhit = kbhit())) ) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); } if (did_kbhit) { nextCharacter = _getch(); // Windows下按下Enter会返回'\r' if (nextCharacter == '\r') { putchar('\n'); } else { putchar(nextCharacter); } } // 检测换行/回车,结束当前行输入 if ((nextCharacter == '\n') or (nextCharacter == '\r')) { break; } synchronousInput += nextCharacter; } if (!continueGettingInput.load()) { break; } // 等待主线程处理完当前输入行 while (continueGettingInput.load() && !sendOverNextLine) { std::this_thread::yield(); } if (!continueGettingInput.load()) { break; } // 线程安全地传递输入内容 inputLock.lock(); input = synchronousInput; inputLock.unlock(); sendOverNextLine = false; } while (continueGettingInput.load()); in_thread = false; }); } ~AsyncGetline() { continueGettingInput = false; if (tj.joinable()) { tj.join(); } } // 获取输入行,无输入时返回特定标记字符串 std::string GetLine() { if (sendOverNextLine) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); return ">>>NO_LINE<<<"; } else { inputLock.lock(); std::string returnInput = input; inputLock.unlock(); sendOverNextLine = true; return returnInput; } } public: std::atomic<bool> continueGettingInput; std::atomic<bool> in_thread{false}; private: std::thread tj; std::atomic<bool> sendOverNextLine; std::mutex inputLock; std::string input; }; class ShellThread { public: std::thread tj; std::atomic<bool> active{false}; std::atomic<bool> prompt_printed{false}; std::string in_repl_cmd; void blocking_process_shell(void); void start(void); void stop(void); }; ShellThread myshell; AsyncGetline* ag{NULL}; void ShellThread::start(void) { active = true; ag = new AsyncGetline(); tj = std::thread(&ShellThread::blocking_process_shell, this); } void ShellThread::stop(void) { active = false; printf("STOPPING "); ag->continueGettingInput = false; while(ag->in_thread.load()) { std::this_thread::sleep_for(std::chrono::milliseconds(5)); } delete ag; if (tj.joinable()) { tj.join(); } } void ShellThread::blocking_process_shell(void) { while( active.load() ) { if (!prompt_printed.load()) { printf("> "); fflush(stdout); prompt_printed = true; } if (ag) { in_repl_cmd = ag->GetLine(); if (in_repl_cmd != ">>>NO_LINE<<<") { printf("# %s\n", in_repl_cmd.c_str()); fflush(stdout); prompt_printed = false; } } else { in_repl_cmd = ""; } std::this_thread::sleep_for(std::chrono::milliseconds(50)); } } int main(void) { myshell.start(); std::this_thread::sleep_for(std::chrono::milliseconds(8000)); myshell.stop(); return EXIT_SUCCESS; }
代码说明
- AsyncGetline类通过
kbhit()周期性检测键盘输入,避免了std::getline的阻塞; - 使用原子变量和互斥锁保证线程安全,确保输入内容的正确传递和线程终止信号的及时响应;
- 平台兼容:Windows直接使用
conio.h,Linux使用模拟实现的linux_conio.h; - 主线程可以随时通过设置
continueGettingInput为false终止输入线程,无需等待用户输入。
内容的提问来源于stack exchange,提问作者sdbbs
相关产品推荐
相关产品推荐

