You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实现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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 09:47:49