如何停止在 C++ Linux 控制台应用程序中读取标准输入的线程?
How to stop a thread reading stdin in a C++ Linux console application?
我正在编写一个控制台应用程序,它接受来自 stdin
的输入(单行命令)。此应用程序在专用线程中读取输入,所有输入都存储在队列中,然后由主线程以安全的方式进行处理。当用户输入 exit 命令时,它被停止侦听新输入的输入线程拦截,该线程被加入主线程,应用程序按要求停止。
现在我正在容器化这个应用程序,但我仍然希望能够附加到容器并从 stdin
输入命令,所以我指定 tty
和 stdin_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 关于如何停止阅读标准输入的内容,接受的答案似乎很有希望,但是:
- 将 ncurses 用于我正在尝试做的事情似乎真的有点矫枉过正
- 如果使用
select()
和描述的超时机制,如果在键入命令时发生超时会发生什么情况?
我也读过 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.
但我不确定这对我有帮助。
我正在考虑解决我的问题的多种可能性:
- 想办法在没有用户交互的情况下将换行符发送到我的应用程序的
stdin
,如果可能的话会很黑,但可能会解锁 fgets
.
- 终止线程,我知道终止线程被认为是一种不好的做法,但由于我在这里所做的唯一事情就是停止应用程序,也许我可以接受它,会有任何副作用吗?我该怎么做?
- 以另一种方式重写用户输入(我还不知道,
jthread
,别的什么?)这将允许 sigTerm()
停止应用程序。
- 也许使用 ncurses(这真的能帮助我通过接收信号来停止应用程序吗?)
- 使用
select()
和超时机制并承受输入中断的风险
- 放弃用户输入并享受一些假期。
您可以在信号处理程序中关闭 stdin
。 fgets
将立即 return(并且大概 return NULL
)。
好消息是 close
在 list of functions that are safe to call from a signal handler 上(这是一个限制性很强的列表)。快乐的日子。
有一个 ,但它看起来很乱,因为您不确定 fgets
在获取它时实际上会 return。
此外,如果您切换到使用 cin
和 getline
,关闭 stdin
应该仍然有效,这肯定会改进您的代码 (*)。当您关闭 stdin
时,可能会 return 并设置 badbit
,尽管代码可以比单独检查它更健壮。也许只是在你的信号处理程序中设置一个 (volatile
) 标志并测试它。
(*) 因为 getline
可以读入 std::string
,这意味着它可以读取任意长行而不用担心分配 fixed-size 缓冲区 'big enough' .
我正在编写一个控制台应用程序,它接受来自 stdin
的输入(单行命令)。此应用程序在专用线程中读取输入,所有输入都存储在队列中,然后由主线程以安全的方式进行处理。当用户输入 exit 命令时,它被停止侦听新输入的输入线程拦截,该线程被加入主线程,应用程序按要求停止。
现在我正在容器化这个应用程序,但我仍然希望能够附加到容器并从 stdin
输入命令,所以我指定 tty
和 stdin_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 关于如何停止阅读标准输入的内容,接受的答案似乎很有希望,但是:
- 将 ncurses 用于我正在尝试做的事情似乎真的有点矫枉过正
- 如果使用
select()
和描述的超时机制,如果在键入命令时发生超时会发生什么情况?
我也读过 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.
但我不确定这对我有帮助。
我正在考虑解决我的问题的多种可能性:
- 想办法在没有用户交互的情况下将换行符发送到我的应用程序的
stdin
,如果可能的话会很黑,但可能会解锁fgets
. - 终止线程,我知道终止线程被认为是一种不好的做法,但由于我在这里所做的唯一事情就是停止应用程序,也许我可以接受它,会有任何副作用吗?我该怎么做?
- 以另一种方式重写用户输入(我还不知道,
jthread
,别的什么?)这将允许sigTerm()
停止应用程序。 - 也许使用 ncurses(这真的能帮助我通过接收信号来停止应用程序吗?)
- 使用
select()
和超时机制并承受输入中断的风险 - 放弃用户输入并享受一些假期。
您可以在信号处理程序中关闭 stdin
。 fgets
将立即 return(并且大概 return NULL
)。
好消息是 close
在 list of functions that are safe to call from a signal handler 上(这是一个限制性很强的列表)。快乐的日子。
有一个 fgets
在获取它时实际上会 return。
此外,如果您切换到使用 cin
和 getline
,关闭 stdin
应该仍然有效,这肯定会改进您的代码 (*)。当您关闭 stdin
时,可能会 return 并设置 badbit
,尽管代码可以比单独检查它更健壮。也许只是在你的信号处理程序中设置一个 (volatile
) 标志并测试它。
(*) 因为 getline
可以读入 std::string
,这意味着它可以读取任意长行而不用担心分配 fixed-size 缓冲区 'big enough' .