子线程中不可直接使用阻塞式 std::cin;必须在主线程处理终端输入,子线程若需读输入应采用 poll()/WaitForMultipleObjects 等可中断、带超时的系统级 I/O 方式,并配合 atomic 控制与 proper join 清理。

子线程里用 std::cin 阻塞会导致整个线程卡死,根本停不下来
这不是“输入慢”的问题,而是 std::cin 默认是阻塞式同步读取:一旦没输完、没按回车、甚至终端被重定向(比如管道或文件输入),线程就彻底挂起,std::thread::join() 会永远等下去,std::thread::detach() 则让程序退出时直接崩溃(资源未释放 + 线程仍在读)。
常见错误现象:std::terminate 被调用、程序无法正常退出、Ctrl+C 无响应、gdb 显示线程卡在 read 系统调用里。
- 别在子线程里直接调用
std::cin >> x或std::getline(std::cin, s) - 如果必须从终端实时读输入,只允许在主线程做——子线程只负责处理,不负责 I/O
- 真要多线程收输入(比如多个设备/网络连接),得换成非阻塞或带超时的 I/O,比如
poll()+STDIN_FILENO(Linux/macOS)或WaitForMultipleObjects(Windows)
std::atomic<bool></bool> 控制循环,但对 std::cin 无效
很多人写 while (running) { std::cin >> x; ... },以为把 running 设为 false 就能跳出。错——std::cin 卡在系统调用里时,C++ 层的变量检查根本不会执行,循环体一次都没进,更别说读新值了。
这种写法只对「计算密集型循环」有效,对「I/O 阻塞型循环」完全失效。
立即学习“C++免费学习笔记(深入)”;
-
std::atomic<bool> running{true}</bool>是必要但不充分条件 - 必须配合可中断的 I/O:要么用信号(
siginterrupt+SIGUSR1),要么换非阻塞模式(fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK)),要么用带超时的轮询 - Linux 下简单方案:用
poll()检查STDIN_FILENO是否就绪,超时设为 100ms,每次就绪再调std::cin(此时保证有数据可读,不会阻塞)
用 poll() 实现可中断的终端输入循环(Linux/macOS)
这是最可控、不依赖第三方库、且能响应停止信号的做法。核心是把“等输入”这件事从 C++ 流层面下沉到系统调用层,由你控制超时和退出逻辑。
#include <poll.h>
#include <iostream>
#include <string>
#include <atomic>
#include <thread>
std::atomic<bool> keep_reading{true};
void input_loop() {
char buf[256];
while (keep_reading) {
struct pollfd pfd = {.fd = STDIN_FILENO, .events = POLLIN};
int ret = poll(&pfd, 1, 100); // 100ms 超时
if (ret == -1) break; // 错误
if (ret == 0) continue; // 超时,继续下一轮
if (pfd.revents & POLLIN) {
if (fgets(buf, sizeof(buf), stdin)) {
std::string line(buf);
// 处理 line...
}
}
}
}
- 注意:不能混用
fgets()和std::cin,它们共享同一个FILE*缓冲区,混用会导致行为未定义 - 如果必须用
std::string,可用std::istringstream解析buf,别用std::cin - Windows 下需换用
WaitForSingleObject(GetStdHandle(STD_INPUT_HANDLE), 100)+PeekConsoleInput,逻辑更复杂
主线程退出前必须显式关闭子线程输入循环
即使用了 poll(),也不能靠“主线程 return”自动清理。子线程可能刚调完 poll() 进入 fgets(),这时主线程已结束,stdin 句柄被关闭,fgets() 返回 nullptr,但线程还在跑空循环,或触发 SIGPIPE。
- 退出前先设
keep_reading = false,再join()——但要确保子线程在poll()返回后能快速检查并退出 - 更稳妥:加一个
std::condition_variable配合std::mutex,主线程 notify,子线程在每次poll()后 wait 带超时,双重保险 - 最容易被忽略的一点:终端输入线程往往还涉及
std::cout输出,多个线程同时写std::cout会乱序甚至崩溃,务必加锁或改用线程安全日志


















