条件变量是一种在并发编程中常用的同步原语。是一种通知机制,一个线程需要某种条件成立后,才能继续执行,如果条件不成立则阻塞等待条件成立,是wait端;另外的线程则是执行某些操作后,使条件成立,然后唤醒等待线程,是signal/broadcast端。
wait端的使用方式:
- 由于条件会被wait线程读取,被signal/broadcast线程修改,即写入。为了防止出现竞争,需要和mutex一起使用,使用mutex来保护条件。
- 在mutex已经锁住的情况下,才能调用wait。
- 由于spurious wakeup(虚假唤醒)的原因,wait函数返回并不代表条件已经成立,在wait函数返回后,需要再次判断条件是否成立。因此需要将wai调用放到while循环中。
wait端检测到条件不成立时,可以调用以下两个函数进行等待。
1 int pthread_cond_timedwait(pthread_cond_t *restrict cond, 2 pthread_mutex_t *restrict mutex, 3 const struct timespec *restrict abstime); 4 int pthread_cond_wait(pthread_cond_t *restrict cond, 5 pthread_mutex_t *restrict mutex);
这两个函数的区别是,第一个函数可以设置超时时间。这里主要说明第一函数的使用方法和我遇到的问题。timespec指定的是超时时刻的绝对时间,而不是相对时间,因此需要先获取当前时间,然后加上需要等待的时间才能得到用于设置的时间,当前时间通过gettimeofday获取。为简化操作,这里把这些操作封装为一个函数,用于设置以毫秒为单位的相对超时时间。我第一次写的获取绝对时间的函数如下:
1 void get_abstime_wait(int microseconds, struct timespec *abstime) 2 { 3 struct timeval tv; 4 int absmsec; 5 gettimeofday(&tv, NULL); 6 absmsec = tv.tv_sec * 1000 + tv.tv_usec / 1000; 7 absmsec += microseconds; 8
9 abstime->tv_sec = absmsec / 1000; 10 abstime->tv_nsec = absmsec % 1000 * 1000000; 11 }
第一个参数指定超时的相对时间,比如传500,表示500毫秒后超时。但是这样写几乎肯定使wait端线程进入busy loop状态。因为这些计算的中间结果,超出了int类型的表示范围,溢出了。添加打印后会发现abstime->tv_sec是一个很小的值,因此调用pthread_cond_timedwait后,立即就超时返回了。因此正确的写法是使用long long来保存结果,long long 类型在linux上是64位的,因此肯定不会溢出。
1 void get_abstime_wait(int microseconds, struct timespec *abstime) 2 { 3 struct timeval tv; 4 long long absmsec; 5 gettimeofday(&tv, NULL); 6 absmsec = tv.tv_sec * 1000ll + tv.tv_usec / 1000ll; 7 absmsec += microseconds; 8
9 abstime->tv_sec = absmsec / 1000ll; 10 abstime->tv_nsec = absmsec % 1000ll * 1000000ll; 11 }
使用pthread_cond_timedwait过程中遇到的另一个奇葩问题是,当时间被设置为1970年以前时,超时机制也会出问题。我的工作是搞嵌入式开发,遇到过一台设备的时钟芯片坏掉后,获取出来的时间有问题,导致时间被设置成了1970年以前,这竟然导致pthread_cond_timedwait长眠不醒。本来打算在ubuntu上重现一下,结果系统不让设置1970年以前的时间了,也许已经不支持1970年以前的时间了吧,就先不管了。如果那位大神知道怎么回事,麻烦通知小弟一声,小弟不甚感激。