我是靠谱客的博主 敏感果汁,最近开发中收集的这篇文章主要介绍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=%dsn", 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=%dsn", 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结构根本不会被改写。注意不要这样使用!

最后

以上就是敏感果汁为你收集整理的sleep(3) 容易被忽视的提前退出的全部内容,希望文章能够帮你解决sleep(3) 容易被忽视的提前退出所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(40)

评论列表共有 0 条评论

立即
投稿
返回
顶部