sem_timedwait等待毫秒

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

sem_wait递减(加锁)由sem指向的信号量。如果该信号量的值大于0,那么递减操作可以完成,并且该函数立即返回。如果这个信号量当前值为0,那么对sem_timedwait的调用将一直阻塞直到可以进行递减操作(例如:该信号量的值增加至大于0),或者是一个信号处理打断该操作。

sem_timedwait和sem_wait一样,除了一点,当递减操作不能立即执行时,sem_timedwait的abs_timeout参数指定了调用应该阻塞的时间限制。abs_timeout参数指向一个结构体,该结构体以自1970-01-01 00:00:00起,过去的秒数和纳秒数指定了一个绝对超时时间。该结构定义如下:

struct timespec
 {
       time_t tv_sec;      /* Seconds */
       long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
};

tv_sec和tv_nsec都是long型。你也许会想,它们会不会溢出?

我们来看看,32位机上,long型通常是32的,那么其能表示的最大值就是0x7FFFFFFF,即十进制的2147483647。以秒为例,这样的值能表示多少年呢?

2147483647/(365*24*60*60) = 2147483647/31536000= 68

自1970年算起,68年后,是2038年,也就是在2038年以前,目前约定的这种用法都是安全的。那么2038年后怎么办呢?这种问题应该不用担心,到那时可能64位机都是古董了……

言归正传,怎么样让sem_timedwait等等毫秒级的时间呢?

直观的方法可能是

在tv_nsec上加上要等等的时间。例如,如果要等带500毫秒,如下使用

struct timespec ts;

clock_gettime(CLOCK_REALTIME, &ts);

ts.tv_nsec += 500*1000*1000;

sem_timedwait(sem,&ts);

当然这是不正确的做法。正如上面描述一样,tv_nsec的取值范围是0 – 999999999,如果tv_nsec的值恰好是999999999(加1就应该往tv_sec进位,然后tv_nsec变为0),那么加上500*1000*1000纳秒,将会产生溢出,大于999999999的部分都会被丢弃,这样的到的新的ts的值时间上比等待前的时间小,sem_timedwait会立即返回出错(不正确的timedout值)。

 

网上搜索到的用法基本都是这样的:

struct timespec ts;

clock_gettime(CLOCK_REALTIME, &ts);

ts.tv_nsec += (ts.tv_sec*1000*1000*1000 +500*1000*1000);

ts.tv_sec = ts.tv_nsec/(1000*1000*1000);

ts.tv_nsec = ts.tv_nsec%(1000*1000*1000);

sem_timedwait(sem, &ts);

这已经大大溢出了long所能表示的最大值,我由于急于使用,搜索了这些方法,结果一个也没成功,都是立即返回。那时既然都没有考虑溢出问题……学而不思则罔。

如果考虑到溢出的话,要实现毫秒级的等待也就不是什么难事了。下面这个函数可以实现秒或毫秒级的等待,msecs参数单位是毫秒,时间是从当前时间算起。

int sem_timedwait_millsecs(sem_t *sem, long msecs)
{
	struct timespec ts;
	clock_gettime(CLOCK_REALTIME, &ts);
	long secs = msecs/1000;
	msecs = msecs%1000;
	
	long add = 0;
	msecs = msecs*1000*1000 + ts.tv_nsec;
	add = msecs / (1000*1000*1000);
	ts.tv_sec += (add + secs);
	ts.tv_nsec = msecs%(1000*1000*1000);

	return sem_timedwait(sem, &ts);
}

上面代码中最优可能溢出的地方是msecs = msecs*1000*1000 + ts.tv_nsec; 那么它会不会溢出呢?经过前面的计算,msecs的最大值为999000000, ts.tv_nsec的最大值是999999999,两者相加为1998999999 < 0x7FFFFFFF,即不会溢出。

sem_timedwait返回值

成功,则返回0. 出错返回-1,并设置errno指明具体的错误

EINTR

调用被信号中断

EINVAL

sem是一个无效的信号量

EAGAIN

操作不能被无阻塞执行(例如信号量的当前值为0)

sem_timedwait还会发生如下错误

EINVAL

abs_timeout.tv_nsec的值小于0,或者大于等于1000 000 000

ETIMEOUT

在信号量可以被锁定前超时了




你可能感兴趣的:(linux/Unix)