你想使用信号还是线程?
首先,设置信号处理程序或准备合适的线程函数;详情请参见man 7 sigevent。
接下来,使用timer_create()
创建一个合适的定时器。详情请参见man 2 timer_create。
根据定时器触发时要执行的操作,您可能希望将定时器设置为单次触发或在短时间间隔后重复触发。您可以使用timer_settime()
同时装载和卸载定时器;有关详细信息,请参见man 2 timer_settime。
在实际应用中,通常需要多路复用定时器。即使进程可以创建多个定时器,但它们是有限资源。特别是超时定时器 - 它们很简单,只需设置一个标志和/或向特定线程发送信号 - 应该使用一个单独的定时器,在下一个超时时触发,设置相关的超时标志,并可选择向所需线程发送一个带有空体处理程序的信号,以确保它被中断。(对于单线程进程,原始信号传递将中断阻塞I/O调用。)考虑一个服务器,响应某些请求:请求本身可能具有大约一分钟左右的超时时间,而处理请求可能需要连接超时、I/O 超时等。
现在,原始问题很有趣,因为定时器在有效使用时非常强大。然而,示例程序基本上是无意义的。为什么不创建一个设置一个或多个定时器的程序,例如输出一些内容到标准输出?请记住使用unistd.h
中的write()
等,因为它们是异步信号安全的,而来自stdio.h
的printf()
等却不是。 (如果您的信号处理程序使用非异步信号安全函数,则结果未定义。通常会起作用,但不能保证;它可能像工作一样崩溃。测试将无法说明问题,因为它是未定义的。)
编辑后添加:这里是多路复用超时的最简示例。
(在法律允许的范围内,我将下面所示的代码片段及其相关邻近权利专属性公共领域全球使用;请参见CC0公共领域奉献。换句话说,请随意使用以下代码,但不要因为它而归咎于我所造成的任何问题。)
我使用了旧版GCC原子内置函数,因此它应该是线程安全的。通过添加一些内容,它也应该适用于多线程代码。(您不能使用例如互斥锁,因为pthread_mutex_lock()
不是异步信号安全的。原子操作超时状态应该可以工作,尽管如果您在它触发时禁用超时,则可能还会存在一些
#define _POSIX_C_SOURCE 200809L
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#define TIMEOUTS 16
#define TIMEOUT_SIGNAL (SIGRTMIN+0)
#define TIMEOUT_USED 1
#define TIMEOUT_ARMED 2
#define TIMEOUT_PASSED 4
static timer_t timeout_timer;
static volatile sig_atomic_t timeout_state[TIMEOUTS] = { 0 };
static struct timespec timeout_time[TIMEOUTS];
static inline double timespec_diff(const struct timespec after, const struct timespec before)
{
return (double)(after.tv_sec - before.tv_sec)
+ (double)(after.tv_nsec - before.tv_nsec) / 1000000000.0;
}
static inline void timespec_add(struct timespec *const to, const double seconds)
{
if (to && seconds > 0.0) {
long s = (long)seconds;
long ns = (long)(0.5 + 1000000000.0 * (seconds - (double)s));
if (ns < 0L)
ns = 0L;
else
if (ns > 999999999L)
ns = 999999999L;
to->tv_sec += (time_t)s;
to->tv_nsec += ns;
if (to->tv_nsec >= 1000000000L) {
to->tv_nsec -= 1000000000L;
to->tv_sec++;
}
}
}
static inline void timespec_set(struct timespec *const to, const double seconds)
{
if (to) {
if (seconds > 0.0) {
const long s = (long)seconds;
long ns = (long)(0.5 + 1000000000.0 * (seconds - (double)s));
if (ns < 0L)
ns = 0L;
else
if (ns > 999999999L)
ns = 999999999L;
to->tv_sec = (time_t)s;
to->tv_nsec = ns;
} else {
to->tv_sec = (time_t)0;
to->tv_nsec = 0L;
}
}
}
static inline int timeout_passed(const int timeout)
{
if (timeout >= 0 && timeout < TIMEOUTS) {
const int state = __sync_or_and_fetch(&timeout_state[timeout], 0);
if (!(state & TIMEOUT_USED))
return -1;
if (!(state & TIMEOUT_ARMED))
return -1;
return (state & TIMEOUT_PASSED) ? 1 : 0;
} else {
return -1;
}
}
static inline int timeout_unset(const int timeout)
{
if (timeout >= 0 && timeout < TIMEOUTS) {
const int state = __sync_fetch_and_and(&timeout_state[timeout], TIMEOUT_PASSED);
if (!(state & TIMEOUT_USED))
return -1;
if (!(state & TIMEOUT_ARMED))
return -1;
return (state & TIMEOUT_PASSED) ? 1 : 0;
} else {
return -1;
}
}
int timeout_set(const double seconds)
{
struct timespec now, then;
struct itimerspec when;
double next;
int timeout, i;
if (seconds <= 0.0)
return -1;
if (clock_gettime(CLOCK_REALTIME, &now))
return -1;
then = now;
timespec_add(&then, seconds);
for (timeout = 0; timeout < TIMEOUTS; timeout++)
if (!(__sync_fetch_and_or(&timeout_state[timeout], TIMEOUT_USED) & TIMEOUT_USED))
break;
if (timeout >= TIMEOUTS)
return -1;
__sync_and_and_fetch(&timeout_state[timeout], TIMEOUT_USED);
timeout_time[timeout] = then;
__sync_or_and_fetch(&timeout_state[timeout], TIMEOUT_ARMED);
next = seconds;
for (i = 0; i < TIMEOUTS; i++)
if ((__sync_fetch_and_or(&timeout_state[i], 0) & (TIMEOUT_USED | TIMEOUT_ARMED | TIMEOUT_PASSED)) == (TIMEOUT_USED | TIMEOUT_ARMED)) {
const double secs = timespec_diff(timeout_time[i], now);
if (secs >= 0.0 && secs < next)
next = secs;
}
timespec_set(&when.it_value, next);
when.it_interval.tv_sec = 0;
when.it_interval.tv_nsec = 0L;
if (timer_settime(timeout_timer, 0, &when, NULL)) {
__sync_and_and_fetch(&timeout_state[timeout], 0);
return -1;
}
return timeout;
}
static void timeout_signal_handler(int signum __attribute__((unused)), siginfo_t *info, void *context __attribute__((unused)))
{
struct timespec now;
struct itimerspec when;
int saved_errno, i;
double next;
if (!info || info->si_code != SI_TIMER)
return;
saved_errno = errno;
if (clock_gettime(CLOCK_REALTIME, &now)) {
errno = saved_errno;
return;
}
next = -1.0;
for (i = 0; i < TIMEOUTS; i++)
if ((__sync_or_and_fetch(&timeout_state[i], 0) & (TIMEOUT_USED | TIMEOUT_ARMED | TIMEOUT_PASSED)) == (TIMEOUT_USED | TIMEOUT_ARMED)) {
const double seconds = timespec_diff(timeout_time[i], now);
if (seconds <= 0.0) {
__sync_or_and_fetch(&timeout_state[i], TIMEOUT_PASSED);
} else
if (next <= 0.0 || seconds < next) {
next = seconds;
}
}
timespec_set(&when.it_value, next);
when.it_interval.tv_sec = 0;
when.it_interval.tv_nsec = 0L;
timer_settime(timeout_timer, 0, &when, NULL);
errno = saved_errno;
}
int timeout_init(void)
{
struct sigaction act;
struct sigevent evt;
struct itimerspec arm;
sigemptyset(&act.sa_mask);
act.sa_sigaction = timeout_signal_handler;
act.sa_flags = SA_SIGINFO;
if (sigaction(TIMEOUT_SIGNAL, &act, NULL))
return errno;
evt.sigev_notify = SIGEV_SIGNAL;
evt.sigev_signo = TIMEOUT_SIGNAL;
evt.sigev_value.sival_ptr = NULL;
if (timer_create(CLOCK_REALTIME, &evt, &timeout_timer))
return errno;
arm.it_value.tv_sec = 0;
arm.it_value.tv_nsec = 0L;
arm.it_interval.tv_sec = 0;
arm.it_interval.tv_nsec = 0L;
if (timer_settime(timeout_timer, 0, &arm, NULL))
return errno;
return 0;
}
int timeout_done(void)
{
struct sigaction act;
struct itimerspec arm;
int errors = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = SIG_IGN;
if (sigaction(TIMEOUT_SIGNAL, &act, NULL))
if (!errors) errors = errno;
arm.it_value.tv_sec = 0;
arm.it_value.tv_nsec = 0L;
arm.it_interval.tv_sec = 0;
arm.it_interval.tv_nsec = 0;
if (timer_settime(timeout_timer, 0, &arm, NULL))
if (!errors) errors = errno;
if (timer_delete(timeout_timer))
if (!errors) errors = errno;
if (errors)
errno = errors;
return errors;
}
在编译时记得加入rt
库,即使用gcc -W -Wall *source*.c -lrt -o *binary*
进行编译。
思路是主程序首先调用timeout_init()
来安装所有必要的处理程序等等,并且可以在退出前(或在fork()
之后的子进程中)调用timeout_done()
进行卸载。
要设置超时,您需要调用timeout_set(seconds)
。返回值是一个超时描述符。目前只有一个标志可以使用timeout_passed()
检查,但超时信号的传递也会中断任何阻塞的I/O调用。因此,您可以期望超时将中断任何阻塞的I/O调用。
如果您想做超时后的操作,您不能在信号处理程序中完成;请记住,在信号处理程序中,您仅限于异步信号安全函数。最简单的方法是使用一个独立的线程,不停地循环执行sigwaitinfo()
,并在所有其他线程中屏蔽TIMEOUT_SIGNAL
信号。这样,专用线程保证捕获信号,但同时不限制于异步信号安全函数。例如,它可以做更多工作,甚至使用pthread_kill()
向特定线程发送信号。(只要该信号有处理程序,即使是一个空函数体的处理程序,其传递也会中断该线程中的任何阻塞I/O调用。)
以下是使用超时的简单main()
示例。这很愚蠢,并依赖于fgets()
不重试(当被信号中断时),但它似乎可以工作。
#include <string.h>
#include <stdio.h>
int main(void)
{
char buffer[1024], *line;
int t1, t2, warned1;
if (timeout_init()) {
fprintf(stderr, "timeout_init(): %s.\n", strerror(errno));
return 1;
}
printf("You have five seconds to type something.\n");
t1 = timeout_set(2.5); warned1 = 0;
t2 = timeout_set(5.0);
line = NULL;
while (1) {
if (timeout_passed(t1)) {
if (!warned1++)
printf("\nTwo and a half seconds left, buddy.\n");
}
if (timeout_passed(t2)) {
printf("\nAw, just forget it, then.\n");
break;
}
line = fgets(buffer, sizeof buffer, stdin);
if (line) {
printf("\nOk, you typed: %s\n", line);
break;
}
}
timeout_unset(t1);
timeout_unset(t2);
if (timeout_done()) {
fprintf(stderr, "timeout_done(): %s.\n", strerror(errno));
return 1;
}
return 0;
}
if (a[i] = number_to_delete)
,可能是想写成if (a[i] == number_to_delete)
。可以参考这里的动态列表实现:http://stackoverflow.com/questions/12652918/making-a-queue-program/12654055#12654055。顺便问一下,这跟定时器有什么关系吗? - Pablocreate_timer
/timer_settime
/delete_timer
系统调用家族。在sigevent中,有一个sival_ptr,可以用来指向包含要删除数组索引和地址的结构体。 - Ducktimer_create
/timer_delete
。 - Duckgetrlimit(RLIMIT_SIGPENDING, &rlim)
和struct rlimit rlim;
),根据当前man timer_create
手册的NOTES部分。内核可能会施加其他系统范围的限制,因此您实际上无法依赖于在实践中获得那么多计时器。 - Nominal Animal