实现C++非阻塞cin输入:解决限时答题游戏超时仍可输入问题
解决限时答题游戏的非阻塞输入问题
你的问题核心在于std::cin >> answer是阻塞式调用——即便计时结束timeOver变为true,答题线程仍会卡在输入等待状态,直到用户完成输入并提交。要实现计时结束立即终止输入,需要改用非阻塞输入方式,同时先修正线程安全问题。
先修正线程安全问题
全局变量timeOver被两个线程同时读写,存在竞态风险,建议替换为原子类型:
#include <atomic> std::atomic<bool> timeOver = false;
简便实现方式:利用kbhit()(跨平台非标准但易用)
kbhit()是多数编译器支持的非标准函数,用于检测是否有键盘输入,无输入时会立即返回,不会阻塞。结合它可以实现非阻塞输入检查:
修改quiz函数如下:
void quiz() { std::string answer; while(!timeOver) { std::cout << "some question?\n"; // 循环检查输入和计时状态 while (!timeOver && !kbhit()) { // 短暂休眠避免CPU空转 std::this_thread::sleep_for(100ms); } if (timeOver) break; // 读取输入并处理 std::cin >> answer; std::cout << "You entered: " << answer << "\n"; } std::cout << "time's up\n"; }
注意:kbhit()在Linux下需包含<cstdio>,Windows下包含<conio.h>,可通过条件编译适配:
#ifdef _WIN32 #include <conio.h> #else #include <cstdio> #define kbhit() getchar() != EOF #endif
平台原生标准实现
如果需要更标准的平台方案,可分别处理:
Linux/macOS平台:修改终端属性
用fcntl将标准输入设置为非阻塞模式,配合select等待输入或超时:
#include <fcntl.h> #include <sys/select.h> void quiz() { std::string answer; // 保存原终端属性,后续恢复 int flags = fcntl(STDIN_FILENO, F_GETFL, 0); fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); while(!timeOver) { std::cout << "some question?\n"; fd_set readfds; FD_ZERO(&readfds); FD_SET(STDIN_FILENO, &readfds); struct timeval tv = {0, 100000}; // 100ms超时 int ret = select(STDIN_FILENO + 1, &readfds, nullptr, nullptr, &tv); if (ret == -1) { perror("select error"); break; } else if (ret > 0 && FD_ISSET(STDIN_FILENO, &readfds)) { // 有输入时读取 if (std::cin >> answer) { std::cout << "You entered: " << answer << "\n"; } } } // 恢复终端原属性 fcntl(STDIN_FILENO, F_SETFL, flags); std::cout << "time's up\n"; }
Windows平台:修改控制台模式
用GetConsoleMode和PeekConsoleInput实现非阻塞输入:
#include <windows.h> void quiz() { std::string answer; HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); DWORD mode; GetConsoleMode(hStdin, &mode); // 设置控制台为非阻塞输入模式 SetConsoleMode(hStdin, mode & ~ENABLE_LINE_INPUT & ~ENABLE_ECHO_INPUT); while(!timeOver) { std::cout << "some question?\n"; INPUT_RECORD ir; DWORD count; // 检查是否有输入事件 if (PeekConsoleInput(hStdin, &ir, 1, &count) && count > 0) { if (ir.EventType == KEY_EVENT && ir.Event.KeyEvent.bKeyDown) { char ch = ir.Event.KeyEvent.uChar.AsciiChar; if (ch == '\r') { // 回车提交答案 std::cout << "\nYou entered: " << answer << "\n"; answer.clear(); } else if (ch == '\b' && !answer.empty()) { // 退格删除 answer.pop_back(); std::cout << "\b \b"; } else if (isprint(ch)) { // 输入可打印字符 answer += ch; std::cout << ch; } } // 消费已检测到的输入事件 ReadConsoleInput(hStdin, &ir, 1, &count); } std::this_thread::sleep_for(100ms); } // 恢复控制台原模式 SetConsoleMode(hStdin, mode); std::cout << "\ntime's up\n"; }
完整优化代码示例(以kbhit()简便方案为例)
#include <iostream> #include <thread> #include <chrono> #include <atomic> #include <cstdio> #ifdef _WIN32 #include <conio.h> #else #define kbhit() getchar() != EOF #endif using namespace std::chrono_literals; std::atomic<bool> timeOver = false; void countDown() { const auto timer_duration = 5s; const auto start = std::chrono::steady_clock::now(); std::chrono::duration<double> time_left = timer_duration - (std::chrono::steady_clock::now() - start); while (time_left > 0s) { std::this_thread::sleep_for(1s); time_left = timer_duration - (std::chrono::steady_clock::now() - start); std::cout << "Time left: " << time_left.count() << "s\n"; } timeOver = true; } void quiz() { std::string answer; while(!timeOver) { std::cout << "some question?\n"; while (!timeOver && !kbhit()) { std::this_thread::sleep_for(100ms); } if (timeOver) break; std::cin >> answer; std::cout << "You submitted: " << answer << "\n"; } std::cout << "time's up\n"; } int main() { std::thread th1(countDown); std::thread th2(quiz); std::cout << "both threads have started\n"; th1.join(); th2.join(); std::cout << "both threads have ended\n"; }
内容的提问来源于stack exchange,提问作者somedude
相关产品推荐
相关产品推荐

