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

如何向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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 14:27:04