如何停止在 C++ Linux 控制台应用程序中读取标准输入的线程?

How to stop a thread reading stdin in a C++ Linux console application?

我正在编写一个控制台应用程序,它接受来自 stdin 的输入(单行命令)。此应用程序在专用线程中读取输入,所有输入都存储在队列中,然后由主线程以安全的方式进行处理。当用户输入 exit 命令时,它被停止侦听新输入的输入线程拦截,该线程被加入主线程,应用程序按要求停止。

现在我正在容器化这个应用程序,但我仍然希望能够附加到容器并从 stdin 输入命令,所以我指定 ttystdin_open true 在我的 docker 撰写服务文件中,这就成功了。

但我也希望 docker compose 能够优雅地停止应用程序,所以我决定在我的应用程序中实现 sigTerm() 以便它可以接收来自 docker 的信号撰写并优雅地停止,但是我停留在那个部分,因为输入线程在等待 stdin 上的输入时阻塞。我可以正确接收信号,这根本不是这里的重点,但我正在寻找一种方法来正确停止我的容器化应用程序,同时仍然能够从键盘输入命令。

我的申请可以这样简化:

void gracefulStop() {
  while (getThreadCount() > 1) { // this function exists somewhere else.
    if (userInputThread.joinable()) {
      userInputThread.join();
      removeFromThreadCount(); // this function exists somewhere else.
    }
    std::this_thread::sleep_for(std::chrono::seconds(1));
  }
  exit(SUCCESS);
}

void sigTerm(int s) {
  // Maybe do some stuff here, but what...

  gracefulStop();
}

void userInputLoopThreadFunc() {
  addToThreadCount(); // this function exists somewhere else.
  while (keepGoing) {
    char buf[4096];
    if (!fgets(buf, sizeof(buf), stdin)) {
      break; // we couldn't read from stdin, stop trying.
    }
    std::string input = std::string(buf); // we received a command
    // Intercept exit command
    if (input.starts_with("exit")) {
      keepGoing = false;
    }
    // IRL there's thread safety 
    userInputQueue.push(input); // this will be processed by mainLoop() later
  }
}

int main(int argc, char **argv) {
  // Register the signal
  signal(SIGTERM, sigTerm);

  // Begin listening to user input
  userInputThread = std::thread(&userInputLoopThreadFunc, this);

  // this mainLoop function contains the core of the application
  // as well as the processing code of the user input
  mainLoop();

  // if mainLoop function returned, we received the 'exit' command
  gracefulStop();
}

我已经阅读了多个 question/answers 之类的 this one about non-blocking user input (the accepted answer advises to use a dedicated thread for input, which is what I am doing), or this other one 关于如何停止阅读标准输入的内容,接受的答案似乎很有希望,但是:

我也读过 c++20 jthread here :

The class jthread represents a single thread of execution. It has the same general behavior as std::thread, except that jthread automatically rejoins on destruction, and can be cancelled/stopped in certain situations.

但我不确定这对我有帮助。

我正在考虑解决我的问题的多种可能性:

您可以在信号处理程序中关闭 stdinfgets 将立即 return(并且大概 return NULL)。

好消息是 closelist of functions that are safe to call from a signal handler 上(这是一个限制性很强的列表)。快乐的日子。

有一个 ,但它看起来很乱,因为您不确定 fgets 在获取它时实际上会 return。

此外,如果您切换到使用 cingetline,关闭 stdin 应该仍然有效,这肯定会改进您的代码 (*)。当您关闭 stdin 时,可能会 return 并设置 badbit,尽管代码可以比单独检查它更健壮。也许只是在你的信号处理程序中设置一个 (volatile) 标志并测试它。

(*) 因为 getline 可以读入 std::string,这意味着它可以读取任意长行而不用担心分配 fixed-size 缓冲区 'big enough' .