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
在信号量可以被锁定前超时了