在多线程编程中,经常需要等待某个条件满足才继续执行,但有时我们也希望在一定时间内条件未满足的情况下超时返回。
现在有下面这样一段代码
//这个宏定义 WAIT_TIMEOUT 用于等待一个条件 (cond) 成立,在指定的超时时间 (timeout) 内等待。
//如果在超时时间内条件未满足,则返回一个超时错误码。
#define WAIT_TIMEOUT(cond,timeout,ret) \
{\
int count = 0;\
while(!(cond) && ((timeout) > count))\
{\
count++;\
usleep(1000);\
}\
(ret) = ((timeout) > count)? 0:TIMEOUT_ERR;\
}
1. cond: 需要等待的条件。条件为假时,进入等待循环。
2. timeout: 最大等待时间,单位为毫秒。
3. ret: 结果变量,如果等待超时则返回 TIMEOUT_ERR,否则返回 0。
WAIT_TIMEOUT 宏虽然简单,但sleep在多线程环境下并不安全。下面将介绍如何使用条件变量 (pthread_cond_t) 来实现更为可靠的超时等待机制。
void wakeUp(pthread_cond_t *cond, pthread_mutex_t *mutex)
{
pthread_mutex_lock(mutex); //加锁
pthread_cond_signal(cond); //条件变量发送信号
pthread_mutex_unlock(mutex); //解锁
}
int waitTimeout(pthread_cond_t *cond, pthread_mutex_t *mutex, unsigned int timeout)
{
int ret;
struct timespec ts;
pthread_mutex_lock(mutex);//加锁
clock_gettime(CLOCK_REALTIME, &ts);//获取当前时间
ts.tv_sec += (__time_t)(timeout / 1000);
ts.tv_nsec += (long)(timeout % 1000) * 1000000; // 毫秒转为纳秒
if (ts.tv_nsec >= 1000000000) {
ts.tv_sec += 1;
ts.tv_nsec -= 1000000000;
}
//阻塞线程并释放互斥锁。当被pthread_cond_signal唤醒时,线程重新获得锁,并继续执行。
ret = pthread_cond_timedwait(cond, mutex, &ts);
pthread_mutex_unlock(mutex);
if(ret == ETIMEDOUT)
return TIMEOUT_ERR;
else
return 0;
}
可以看到
首先,函数锁定互斥锁 mutex,保护共享资源。
使用 clock_gettime(CLOCK_REALTIME, &ts); 获取当前的绝对时间,并将超时时间加到 ts 中。这样 ts 变量将代表线程等待的绝对超时时间点。
pthread_cond_timedwait(cond, mutex, &ts);
线程将等待条件变量 cond 被其他线程唤醒,或者直到达到 ts 所代表的时间。
注意,调用 pthread_cond_timedwait 时,互斥锁会被自动释放,当线程被唤醒后,互斥锁会被重新锁定。
在等待完毕后,无论是否超时,都会解锁互斥锁。
如果 pthread_cond_timedwait 返回 ETIMEDOUT,说明等待超时,函数返回 TIMEOUT_ERR。否则返回 0。
而 wakeUp中就是发送条件变量,在满足条件,不需要等待时使用pthread_cond_signal(cond);
总结
使用条件变量不仅提供了超时机制,还保证了多线程环境下的安全性。在需要等待条件的场景中,建议优先使用条件变量来代替简单的轮询或超时等待。
通过这一示例,可以轻松理解如何在 C 语言的多线程编程中有效地使用条件变量和互斥锁来处理复杂的同步问题。