设计思路
- 基于pthread_cond_timedwait实现:在Linux下,使用
pthread_cond_timedwait
函数来实现条件变量的超时等待。这个函数允许设置一个绝对时间作为超时时间,当到达这个时间点或者条件变量被唤醒时,函数返回。
- 时钟选择:选择合适的时钟来获取时间。由于存在时钟漂移,建议使用
CLOCK_MONOTONIC
时钟,它表示从系统启动以来的单调递增时间,不受系统时间调整的影响,能提供相对稳定的时间参考。
- 资源竞争处理:在等待条件变量时,线程通常会持有一个互斥锁。为避免死锁,在调用
pthread_cond_timedwait
前应先获取互斥锁,函数内部会自动释放互斥锁并等待条件变量,当被唤醒或超时时,又会重新获取互斥锁。
关键参数选择
- 超时时间:根据系统业务需求和网络延迟等实际情况来确定。例如,如果网络请求的最大预期延迟是10秒,那么超时时间可以设置为略大于10秒,如12秒,以应对可能的突发延迟。
- 互斥锁类型:对于简单的场景,普通的互斥锁
PTHREAD_MUTEX_DEFAULT
可能就足够。但如果系统中存在大量的线程竞争,可能需要考虑使用递归互斥锁PTHREAD_MUTEX_RECURSIVE
或读写锁(如果读操作远多于写操作),以提高性能。
可能遇到的问题及解决方案
- 虚假唤醒:即使没有其他线程显式唤醒条件变量,
pthread_cond_timedwait
也可能返回。解决方案是在条件变量被唤醒后,再次检查等待的条件是否满足,如果不满足则继续等待。例如:
while (!condition_met) {
struct timespec timeout;
clock_gettime(CLOCK_MONOTONIC, &timeout);
timeout.tv_sec += 12; // 设置12秒超时
int ret = pthread_cond_timedwait(&cond, &mutex, &timeout);
if (ret == ETIMEDOUT) {
// 超时处理
break;
}
}
- 时钟漂移累积:虽然
CLOCK_MONOTONIC
能减少时钟漂移影响,但长时间运行仍可能存在累积问题。可以定期与其他可靠时钟源(如网络时间协议NTP)同步来校正时间。
- 资源泄漏:如果在等待过程中发生异常,例如线程崩溃,可能导致互斥锁未释放。可以使用线程清理函数
pthread_cleanup_push
和pthread_cleanup_pop
来确保在异常情况下互斥锁能被正确释放。
pthread_cleanup_push((void (*)(void*))pthread_mutex_unlock, &mutex);
while (!condition_met) {
struct timespec timeout;
clock_gettime(CLOCK_MONOTONIC, &timeout);
timeout.tv_sec += 12;
int ret = pthread_cond_timedwait(&cond, &mutex, &timeout);
if (ret == ETIMEDOUT) {
break;
}
}
pthread_cleanup_pop(0);