概述
数字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) 容易被忽视的提前退出所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复