优雅地退出多线程进程

发布于 2024-09-28 11:51:07 字数 394 浏览 2 评论 0原文

我正在运行一个多线程 C 程序(进程?),利用信号量和信号量线程。线程不断交互、阻塞、唤醒和恢复。连续在标准输出上打印提示,无需任何人为干预。我希望能够通过按键盘字符退出此进程(在打印消息并放下所有线程后优雅地退出,而不是通过粗略的 CTRL+C SIGINT)喜欢 #。

我可以通过哪些选项来获取用户的此类输入?

我还可以提供哪些相关信息来帮助解决这个问题?

编辑: 你所有的答案听起来都很有趣,但我的主要问题仍然存在。当我不知道当前正在执行哪个线程时,如何获取用户输入?此外,如果通过 SIGINT 发出信号,则使用 sem_wait() 进行的信号量阻塞会中断,这可能会导致死锁。

I'm running a multi-threaded C program (process?) , making use of semaphores & pthreads. The threads keep interacting, blocking, waking & printing prompts on stdout continuously, without any human intervention. I want to be able to exit this process (gracefully after printing a message & putting down all threads, not via a crude CTRL+C SIGINT) by pressing a keyboard character like #.

What are my options for getting such an input from the user?

What more relevant information could I provide that will help to solve this problem?

Edit:
All your answers sound interesting, but my primary question remains. How do I get user input, when I don't know which thread is currently executing? Also, semaphore blocking using sem_wait() breaks if signalled via SIGINT, which may cause a deadlock.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

£噩梦荏苒 2024-10-05 11:51:08

从线程读取标准输入没有区别,除非有多个线程试图同时读取它。不过,很可能您的线程并不总是调用函数来读取标准输入。

如果您经常需要读取用户的输入,您可能希望有一个线程只读取此输入,然后根据此输入设置标志或将事件发布到其他线程。

如果终止字符是您唯一想要的,或者如果这只是用于调试,那么您可能想做的是偶尔轮询标准输入上的新数据。您可以通过将标准输入设置为非阻塞并尝试偶尔读取它来做到这一点。如果读取返回 0 个读取字符,则表示没有按下任何键。但这种方法存在一些问题。在将底层文件描述符(int)设置为非阻塞后,我从未在 FILE * 上使用过 stdio.h 函数,但怀疑它们可能表现得很奇怪。您可以避免使用 stdio 函数并使用 read 来避免这种情况。我曾经读到过一个问题,如果您分叉并执行一个有权访问该文件描述符版本的新程序,则另一个进程可能会更改块/非块标志。我不确定这是否是所有系统上的问题。非阻塞模式可以通过“fcntl”调用来设置或清除。

但是您可以使用具有非常小的超时 (0) 的轮询函数之一来查看是否有数据准备就绪。 poll 系统调用可能是最简单的,但还有 select。各种操作系统还有其他轮询功能。

#include <poll.h>

...
/* return 0 if no data is available on stdin.
        > 0 if there is data ready
        < 0 if there is an error
*/
int poll_stdin(void) {
    struct pollfd pfd = { .fd = 0, .events = POLLIN };

    /* Since we only ask for POLLIN we assume that that was the only thing that
     * the kernel would have put in pfd.revents */
    return = poll(&pfd, 1, 0);
}

您可以在其中一个线程中调用此函数,直到它返回 0,您就可以继续运行。当它返回正数时,您需要从 stdin 读取一个字符来查看它是什么。请注意,如果您在其他地方的 stdin 上使用 stdio 函数,实际上可能已经在新字符前面缓冲了其他字符。 poll 告诉您操作系统为您提供了新的东西,而不是 C 的 stdio 拥有的东西。

如果您经常从其他线程中的标准输入中读取内容,那么事情就会变得混乱。我假设你没有这样做(因为如果你这样做并且它工作正常你可能不会问这个问题)。

There is no difference in reading standard input from threads except if more than one thread is trying to read it at the same time. Most likely your threads are not all calling functions to read standard input all the time, though.

If you regularly need to read input from the user you might want to have one thread that just reads this input and then sets flags or posts events to other threads based on this input.

If the kill character is the only thing you want or if this is just going to be used for debugging then what you probably want to do is occasionally poll for new data on standard input. You can do this either by setting up standard input as non-blocking and try to read from it occasionally. If reads return 0 characters read then no keys were pressed. This method has some problems, though. I've never used stdio.h functions on a FILE * after having set the underlying file descriptor (an int) to non-blocking, but suspect that they may act odd. You could avoid the use of the stdio functions and use read to avoid this. There is still an issue I read about once where the block/non-block flag could be changed by another process if you forked and exec-ed a new program that had access to a version of that file descriptor. I'm not sure if this is a problem on all systems. Nonblocking mode can be set or cleared with a 'fcntl' call.

But you could use one of the polling functions with a very small (0) timeout to see if there is data ready. The poll system call is probably the simplest, but there is also select. Various operating systems have other polling functions.

#include <poll.h>

...
/* return 0 if no data is available on stdin.
        > 0 if there is data ready
        < 0 if there is an error
*/
int poll_stdin(void) {
    struct pollfd pfd = { .fd = 0, .events = POLLIN };

    /* Since we only ask for POLLIN we assume that that was the only thing that
     * the kernel would have put in pfd.revents */
    return = poll(&pfd, 1, 0);
}

You can call this function within one of your threads until and as long as it retuns 0 you just keep on going. When it returns a positive number then you need to read a character from stdin to see what that was. Note that if you are using the stdio functions on stdin elsewhere there could actually be other characters already buffered up in front of the new character. poll tells you that the operating system has something new for you, not what C's stdio has.

If you are regularly reading from standard input in other threads then things just get messy. I'm assuming you aren't doing that (because if you are and it works correctly you probably wouldn't be asking this question).

沦落红尘 2024-10-05 11:51:08

您将有一个线程侦听键盘输入,然后当接收到 # 作为输入时,它会 join() 其他线程。

另一种方法是捕获SIGINT并用它来处理关闭您的应用程序。

You would have a thread listening for keyboard input, and then it would join() the other threads when receiving # as input.

Another way is to trap SIGINT and use it to handle the shutdown of your application.

忆伤 2024-10-05 11:51:08

我这样做的方法是保留一个全局 int“should_die”或其他东西,其范围是 0 或 1,以及另一个全局 int“died”,它跟踪终止的线程数。 should_die 和 dead 最初都为零。您还需要两个信号量来提供全局变量的互斥体。

在某个时刻,线程检查 should_die 变量(当然是在获取互斥体之后)。如果它死亡,它会获取 dead_mutex,增加死亡计数,释放 dead_mutex,然后死亡。

主初始线程定期唤醒,检查已死亡的线程数是否小于线程数,然后返回睡眠状态。当所有其他线程签入后,主线程就会死亡。

如果主线程本身没有生成所有线程,则需要进行一个小修改,将“threads_alive”而不是“died”。当线程分叉时,threads_alive 会增加,当线程死亡时,threads_alive 会减少。

一般来说,干净地终止多线程操作是一件令人痛苦的事情,除了可以使用信号量屏障设计模式等特殊情况之外,这是我听说过的最好的。如果你能找到一个更好、更干净的,我很想听听。

〜anjruu

The way I would do it is to keep a global int "should_die" or something, whose range is 0 or 1, and another global int "died," which keeps track of the number of threads terminated. should_die and died are both initially zero. You'll also need two semaphores to provide mutex around the globals.

At a certain point, a thread checks the should_die variable (after acquiring the mutex, of course). If it should die, it acquires the died_mutex, ups the died count, releases the died_mutex, and dies.

The main initial thread periodically wakes up, checks that the number of threads that have died is less than the number of threads, and goes back to sleep. The main thread dies when all the other threads have checked in.

If the main thread doesn't spawn all the threads itself, a small modification would be to have "threads_alive" instead of "died". threads_alive is incremented when a thread forks, and decremented when the thread dies.

In general, terminating a multithreaded operation cleanly is a pain in the butt, and besides special cases where you can use things like the semaphore barrier design pattern, this is the best I've heard of. I'd love to hear it if you find a better, cleaner one.

~anjruu

前事休说 2024-10-05 11:51:08

一般来说,我有线程等待一组事件,其中一个事件是终止事件。

在主线程中,当我触发终止事件时,我会等待所有退出的线程。

In general, I have threads waiting on a set of events and one of those events is the termination event.

In the main thread, when I have triggered the termination event, I then wait on all the threads having exited.

莫相离 2024-10-05 11:51:08

SIGINT 实际上并不难处理,通常用于优雅终止。您需要一个信号处理程序和一种方法来告诉所有线程该停止了。线程在其循环和信号处理程序集中检查的一个全局标志可能会执行此操作。同样的方法适用于“根据用户命令”终止,尽管您需要一种从终端获取输入的方法 - 要么在专用线程中轮询,要么再次设置终端为您生成信号。

棘手的部分是解除等待线程的阻塞。您必须仔细设计谁告诉谁停止以及他们需要做什么的通知协议 - 将虚拟消息放入队列,设置标志并发出简历信号等。

SIGINT is actually not that difficult to handle and is often used for graceful termination. You need a signal handler and a way to tell all the threads that it's time to stop. One global flag that threads check in their loops and the signal handler sets might do. Same approach works for "on user command" termination, though you need a way to get the input from the terminal - either poll in a dedicated thread, or again, set the terminal to generate a signal for you.

The tricky part is to unblock waiting threads. You have to carefully design the notification protocol of who tells who to stop and what they need to do - put dummy message into a queue, set a flag and signal a cv, etc.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文