sleep(3) 容易被忽视的提前退出

数字3的含义

在使用man查看命令/函数的使用方法时,经常会看到命令名称后紧跟一个带有括号的数字,比如本文所说的sleep(3)

在linux man的解释中可以看到

The table below shows the section numbers of the manual followed by the types of pages they contain.

       1   Executable programs or shell commands
       2   System calls (functions provided by the kernel)
       3   Library calls (functions within program libraries)
       4   Special files (usually found in /dev)
       5   File formats and conventions eg /etc/passwd
       6   Games
       7   Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7)
       8   System administration commands (usually only for root)
       9   Kernel routines [Non standard]

所以我们这里讨论的sleep是一个库函数

问题现象

一个c程序在使用sleep定时的时候发现会提前退出

于是查看man关于sleep的描述

SLEEP(3)                                                                               Linux Programmer's Manual                                                                              SLEEP(3)

NAME
       sleep - sleep for the specified number of seconds

SYNOPSIS
       #include <unistd.h>

       unsigned int sleep(unsigned int seconds);

DESCRIPTION
       sleep() makes the calling thread sleep until seconds seconds have elapsed or a signal arrives which is not ignored.

RETURN VALUE
       Zero if the requested time has elapsed, or the number of seconds left to sleep, if the call was interrupted by a signal handler.

 非常关键一句话“or a signal arrives which is not ignored.”

说明很有可能是代码中注册了某些信号的处理函数,并且在sleep过程中有信号到来,查代码确认确实如此。

场景复现

我们来写一个处理sigalrm信号并且使用了sleep的c程序。

#include <stdio.h>
#include <signal.h>
#include <time.h>

void handle_SIGALRM(int signo) {
    return;
}

int main() {
    signal(SIGALRM, handle_SIGALRM);
    int count = 0;
    while (1) {
        int start_time = time(0);
        sleep(10);
        int current_time = time(0);
        printf("count=%d cost=%ds\n", count++, current_time - start_time);
    }
    return 0;
}

 正常的运行状态应该是这样的

./sleep_sig_alrm_test 
count=0 cost=10s
count=1 cost=10s

 可以看到,每10s输出一次

但是当我们向这个进程发送sigalrm信号时

count=19 cost=10s
count=20 cost=10s
count=21 cost=3s

 可以看到第21次sleep提前退出了

最佳实践

我们看到man手册中描述sleep的返回值是这样说的

Zero if the requested time has elapsed, or the number of seconds left to sleep也就是说如果提前返回的话,返回值是剩下没有sleep完的秒数

所以我们可以实现一个safe_sleep函数

void safe_sleep(int seconds) {
    do {
        seconds = sleep(seconds);
    } while (seconds > 0);
}

 再用safe_sleep重新实现一下这个程序

#include <stdio.h>
#include <signal.h>
#include <time.h>

void handle_SIGALRM(int signo) {
    return;
}

void safe_sleep(int seconds) {
    do {
        seconds = sleep(seconds);
    } while (seconds > 0);
}

int main() {
    signal(SIGALRM, handle_SIGALRM);
    int count = 0;
    while (1) {
        int start_time = time(0);
        safe_sleep(10);
        int current_time = time(0);
        printf("count=%d cost=%ds\n", count++, current_time - start_time);
    }
    return 0;
}

 这时再向运行的进程发送sigalrm信号

count=0 cost=10s
count=1 cost=10s
count=2 cost=9s
count=3 cost=10s

 会发现睡眠时间虽然不能精确到10s,但是误差会比之前少

usleep

如果从精确程度的角度来考虑,自然会想到usleep

但是这里有三个要注意的点

1. usleep同样会由于signal提前退出

2. usleep的返回值不会标记剩余时间

3. 很多人会忽略,usleep的参数范围是[0, 1000000)

ERRORS
       EINTR  Interrupted by a signal; see signal(7).

       EINVAL usec is not smaller than 1000000.  (On systems where that is considered an error.)

 针对第三点,经过测试,在当前的linux系统上,usleep > 1000000时是可以正常工作,并没有报错

stackoverflow上专门有一个帖子讨论这个问题

c++ - Is it safe to use usleep in Ubuntu with value greater than 1000000 - Stack Overflow

尽管在linux上的表现是可用的,但是出于可移植性的考虑,我们不能假设可以传入大于等于100w的参数。

更高精度计时(请不要用,c - Time remaining on a select() call - Stack Overflow)

可以使用select系统调用进行计时 

void safe_sleep(int seconds) {
    struct timeval tv;
    tv.tv_sec = seconds;
    tv.tv_usec = 0;
    int err;
    do {
        err = select(0, NULL, NULL, NULL, &tv);
    } while (err < 0 && errno == EINTR);
}

 这种计时方案不能跨平台,另外在arm架构上测试发现tv结构根本不会被改写。注意不要这样使用!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值