如果我向卡在 pthread_cond_wait() 上的线程发送 SIGINT 信号,当 sign_handler() 返回时,pthread_cond_wait() 是否也会返回?
没有。
如果没有,有什么方法可以让 pthread_cond_wait() 返回?
不,您试图使用错误的工具来解决您遇到的任何潜在问题。
(从技术上讲,pthread_cond_timedwait()允许在被信号传递中断时返回,但它不会这样做,至少在运行内核 5.3.0 的 x86-64 上使用 GNU glibc 2.27 时. 是的,我查过了。)
如何解决我的问题?
让我们假设条件变量是您的用例的最佳选择。 (不过,这只是一个猜测;您没有告诉我们您要解决的真正问题,只是您选择的解决方案如何不起作用。)
然后,推荐的解决方案是使用辅助线程来捕获信号,例如 SIGINT,使用 sigwaitinfo() 或 sigtimedwait()。然后,该帮助线程可以设置特定的volatile sig_atomic_t you_need_to_exit 标志,并在相关条件变量上设置pthread_cond_signal() 或pthread_cond_broadcast(),让他们知道发生了重要的事情。那些等待条件变量的人显然应该首先检查辅助标志;如果设置,则假设这是唤醒信号的来源。通常我将这些标志命名为need_to_exit 或类似名称。
这种信号处理辅助线程的关键是信号需要在所有线程中被阻塞(包括处理辅助线程本身)。最好在创建任何其他线程之前在主线程中执行此操作,因为创建的线程会继承相同的信号掩码。
siginfo_t 结构包含各种有用的信息。最有用的可能是.si_pid 字段,它告诉哪个进程(或0,如果是内核)发送了信号。这样,如果您出于内部目的使用 SIGRTMIN+0 到 SIGRTMAX-0 信号,则可以忽略它们,除非它们来自进程本身(其他线程,.si_pid == getpid())。
线程取消(延迟,在取消点;pthread_cond_wait() 是取消点)是另一种选择。如果线程被取消,您可以使用 pthread_cleanup_push() 设置/添加要运行的函数。这基本上是强行杀死目标线程,但它可以运行它在死亡之前设置的任何清理功能。您还可以使用 pthread_cleanup_pop() 解散任何清理函数——一个参数指定清理函数是运行并丢弃,还是仅仅丢弃。但是,当取消时,线程总是会死掉。
请使用pthread_attr_t 将堆栈大小限制为2 的合理幂。如果堆栈上没有任何大型数组或结构(局部变量),那么类似
#include <limits.h>
#ifndef THREAD_STACK_SIZE
#define THREAD_STACK_SIZE (4 * PTHREAD_STACK_MIN)
#endif
与
sigset_t mask;
pthread_attr_t attrs;
int err;
/* Block SIGINT in this (and all created threads) */
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
err = pthread_sigmask(SIG_BLOCK, &mask, NULL);
if (err) {
fprintf(stderr, "Cannot block signals: %s.\n", strerror(err));
return EXIT_FAILURE;
}
/* Create stack size attribute. */
pthread_attr_init(&attrs);
pthread_attr_setstacksize(&attrs, THREAD_STACK_SIZE);
/*
* Create threads, use &attrs for the second parameter.
*/
/* Optional cleanup - it's a good idea to be careful. */
pthread_attr_destroy(&attrs);
对于堆栈大小和阻塞所有线程中的某些信号来说应该可以正常工作(通过首先在创建其他线程的线程中阻塞它们;它们将继承信号掩码)。
您可以将任何其他信号添加到您喜欢的屏蔽掩码中,但 SIGKILL 和 SIGSTOP 不能被屏蔽、捕获或忽略。
pthread_create() 的第二个参数pthread_attr_t 只是设置(或属性)的集合,例如配置;它们不会被 pthread_create() 调用“消耗”。您可以多次使用同一组属性。这仅包含所需的堆栈大小。 (它不包含堆栈本身,只包含所需的大小。)
默认堆栈大小非常大,通常为 8 MiB,这意味着大量虚拟内存毫无理由地保留给线程堆栈,真的。此外,它严重限制了进程可以创建的线程数。
在许多方面,拥有一个用于信号处理的辅助线程比实际的信号处理程序更容易,因为只有async-signal safe functions 在信号处理程序中使用是安全的;而在辅助线程中,您可以使用 all。