欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

linux下模拟实现sleep以及sleep会出现的问题解决方案

程序员文章站 2022-05-01 15:55:26
...

linux下的sleep这个函数相信我们都不陌生了,它的作用就是让程序休眠一定的秒数,到时间后自动恢复。


这里我们主要用到三个函数:

(1)alarm()函数

 #include <unistd.h>

       unsigned int alarm(unsigned int seconds);
采用alarm()函数设定需要睡眠的秒数,到时间后闹钟会发送SIGALRM信号给当前进程。但SIGALRM信号的默认操作是杀死进程,所以我们需要对SIGALRM信号进行自定义处理。 

(2)pause()函数

#include <unistd.h>
int pause(void);
pause函数使调⽤进程挂起直到有信号递达。如果信号的处理动作是终⽌进程,则进程终⽌,pause函数没有机会返回,所以这里我们要捕捉SIGALRM信号,不让它直接终止进程。

(3)sigaction()函数

  #include <signal.h>

       int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);


具体实现代码:

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

void handler(int sig)//程序在睡眠区间什么也不做,这里自定义也不执行任何操作
{
	
}

int mysleep(int timecount)
{
	struct sigaction act,oact;
	act.sa_handler = handler;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);
	unsigned int unslept = 0;

	sigaction(SIGALRM,&act,&oact);//如果不捕捉,SIGALRM信号直接将进程终止,

	alarm(timecount);//timecount秒后发送一个SIGALRM信号。

	pause(); //将进程挂起,直到有信号递达。

	unslept = alarm(0); //参数为0表示清空闹钟。返回值表示上次闹钟剩下的时间。

	sigaction(SIGALRM,&oact,NULL); // 恢复默认信号处理动作

	return unslept;
}


int main()
{
	while(1)
	{
		unsigned int time = mysleep(5);
		printf("time = %d\n",time);
		printf("hello world\n");
	}
	return 0;
}


竟态条件:
由于异步事件在任何时候都有可能发生(这里的异步事件指出现更优 先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件 (Race Condition)。


虽然alarm(timecount)紧接着的下一行就是pause(),但是无法保证pause()一定会在调用alarm(timecount)之 后的timecount秒之内被调用。

在调用pause之前屏蔽SIGALRM信号使它不能提前递达就可以了。 
1. 屏蔽SIGALRM信号;
2. alarm(nsecs);
3. 解除对SIGALRM信号的屏蔽;
4. pause();
从解除信号屏蔽到调用pause之间存在间隙,SIGALRM仍有可能在这个间隙递达。要是“解除信号屏蔽”和“挂起等待信号”这两步能合并成一个原子操作就好了,这正是sigsuspend
函数的功 能。sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调用sigsuspend不是pause。

#include <signal.h>
int sigsuspend(const sigset_t *sigmask); 


调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时解除对某 个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原
来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。

改良代码:

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

void handler(int sig)//程序在睡眠区间什么也不做,这里自定义也不执行任何操作
{
	
}

//版本一有问题:如果设置好闹钟,但是进程被切出去了,有可能当切回来的时候,闹钟已经响了。要保证闹钟在切回来之前不可能响。
//在切回来之前先屏蔽信号,切回来之后取消屏蔽。
int mysleep(int timecount)
{
	struct sigaction act,oact;
	act.sa_handler = handler;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);
	unsigned int unslept = 0;
	sigaction(SIGALRM,&act,&oact); //注册信号捕捉函数。


	sigset_t newmask,oldmask,suspmask;  //定义信号集。
	sigemptyset(&newmask); //初始化。
	sigaddset(&newmask,SIGALRM); 
	sigprocmask(SIG_BLOCK,&newmask,&oldmask);//将这个信号加入到屏蔽字当中。

	alarm(timecount);//设置好闹钟,下一步就需要屏蔽闹钟信号了,防止当闹钟设置好就被切出去了,从这里可以看出屏蔽信号应该放在设置闹钟的前面,要不然闹钟已经响了,没有执行屏蔽信号代码,导致再切回来的时候等不到闹钟了。

	suspmask = oldmask;
	sigdelset(&suspmask,SIGALRM); //确保suspmask中没有屏蔽SIGALRM信号。
	sigsuspend(&suspmask);//用suspmask去替换block表,从而临时解除对SIGALRM信号的阻塞

	unslept = alarm(0);//取消闹钟。
	sigaction(SIGALRM,&oact,NULL);
	sigprocmask(SIG_SETMASK,&oldmask,NULL);  //恢复之前的系统默认信号和默认信号处理。

	return unslept;


}


int main()
{
	while(1)
	{
		unsigned int time = mysleep(5);
		printf("time = %d\n",time);
		printf("hello world\n");
	}
	return 0;
}


linux下模拟实现sleep以及sleep会出现的问题解决方案





相关标签: linux 线程安全