二进制信号量Wait函数偶尔提前返回问题排查求助
问题描述
实现的二进制信号量有时无法等待指定时长,存在等待时间不足的情况,需定位binarySemaphoreWait函数偶尔提前返回的原因。
代码实现
#define SEMAPHORE_MAXWAIT -1 #define SEMAPHORE_NOWAIT 0 typedef struct binary_semaphore { pthread_mutex_t mutex; pthread_cond_t condvar; bool variable; }binary_semaphore_t; static struct timespec *timespec_addms(struct timespec *ts, unsigned ms) { uint64_t nsec; if(ts) { ts -> tv_sec += ms / 1000; nsec = ts -> tv_nsec + (ms % 1000) * 1000000ULL; ts -> tv_sec += nsec / 1000000000ULL; ts -> tv_nsec = nsec % 1000000000ULL; } return ts; } static int mutex_lockwait(pthread_mutex_t *mutex, unsigned wait) { int result = 0; int line; struct timespec timeOut; if(mutex) { if(result = clock_gettime(CLOCK_REALTIME, &timeOut)) { line = __LINE__; goto exit_cleanup; } timespec_addms(&timeOut, wait); switch(wait) { case SEMAPHORE_NOWAIT: result = pthread_mutex_trylock(mutex); line = __LINE__; break; case SEMAPHORE_MAXWAIT: result = pthread_mutex_lock(mutex); line = __LINE__; break; default: result = pthread_mutex_timedlock(mutex, &timeOut); line = __LINE__; break; } } else result = EFAULT; exit_cleanup: if(result) { if(result != EBUSY) { printError("[MUTEX]", line, __FUNCTION__); } } return result; } static int64_t timespec_diff(const struct timespec *t1, const struct timespec *t2) { int64_t nanodiff = -1; //t1 has to be later than t2 if(t1 -> tv_sec >= t2 -> tv_sec) { nanodiff = (t1 -> tv_sec - t2 -> tv_sec) * 1000000000ULL; if(t1 -> tv_nsec > t2 -> tv_nsec) { nanodiff += t1 -> tv_nsec - t2 -> tv_nsec; } else { if(!nanodiff) { nanodiff = -1; } else { nanodiff -= 1000000000ULL + (t1 -> tv_nsec - t2 -> tv_nsec); } } } return nanodiff; } static struct timespec nanoToTimespec(int64_t nanosec) { return (struct timespec){.tv_sec = nanosec / 1000000000ULL, .tv_nsec = nanosec % 1000000000ULL}; } int binarySemaphoreWait(struct binary_semaphore *p, unsigned wait) { int result= -1; struct timespec ts, ts1; if(!p) goto function_return; if((result = clock_gettime(CLOCK_REALTIME, &ts))) goto function_return; if((result = mutex_lockwait(&p -> mutex, wait))) goto function_return; while (!p->variable) { if((result = clock_gettime(CLOCK_REALTIME, &ts1))) goto cleanup_exit; int64_t nanodiff = timespec_diff(&ts1, &ts); if(nanodiff > 0 && (nanodiff / 1000000ULL) < wait) { timespec_addms(&ts, wait - nanodiff / 1000000ULL); pthread_cond_timedwait(&p->condvar, &p->mutex, &ts); } } p->variable = false; cleanup_exit: pthread_mutex_unlock(&p->mutex); function_return: return result; } int binarySemaphorePost(struct binary_semaphore *p) { int result = -1; if(!p) goto cleanup_exit; result = mutex_lockwait(&p -> mutex, SEMAPHORE_MAXWAIT); if(result) goto cleanup_exit; if (p->variable) { result = EAGAIN; goto cleanup_exit; } p->variable = true; if((result = pthread_cond_signal(&p->condvar))) goto cleanup_exit; if((result = pthread_mutex_unlock(&p->mutex))) goto cleanup_exit; cleanup_exit: return result; }
问题原因分析
1. 超时时间计算逻辑错误
binarySemaphoreWait中,初始ts是调用clock_gettime获取的当前时间,但mutex_lockwait可能已经消耗了部分等待时长(比如调用pthread_mutex_timedlock等待锁的过程)。后续计算剩余时间时,代码通过timespec_diff得到已耗时,然后将ts更新为ts + 剩余时间,这相当于把超时时间向后累加,而非使用固定的绝对超时时间(初始时间+指定等待时长)。这种方式会导致超时逻辑混乱,在时间差计算出错时直接触发提前退出。
2. pthread_cond_timedwait返回值未处理
代码调用pthread_cond_timedwait后没有检查返回值:
- 如果函数返回
ETIMEDOUT,说明已到超时时间,应该退出循环并返回超时错误; - 如果返回其他错误(比如
EINVAL),也需要处理并退出。
当前逻辑下,即使超时,循环仍会继续,可能错误判断信号量状态,导致提前返回。
3. timespec_diff函数逻辑缺陷
当t1的秒数等于t2,但纳秒数小于等于t2时,函数返回-1。此时binarySemaphoreWait中的条件nanodiff > 0不成立,会跳过pthread_cond_timedwait调用,直接继续循环。而while (!p->variable)的条件仍然成立,会直接退出循环,导致信号量未被唤醒也未等待足够时长就提前返回。
4. 类型不匹配导致的分支错误
SEMAPHORE_MAXWAIT定义为-1,但mutex_lockwait的wait参数是unsigned类型。-1转换为unsigned后是UINT_MAX,与switch中case SEMAPHORE_MAXWAIT的int类型-1不匹配,会走到default分支,错误调用pthread_mutex_timedlock而非pthread_mutex_lock,可能导致锁等待超时。
修复建议
修正超时时间计算:
在binarySemaphoreWait开头直接计算固定的绝对超时时间,后续循环不再修改:struct timespec timeout_ts; if((result = clock_gettime(CLOCK_REALTIME, &timeout_ts))) goto function_return; timespec_addms(&timeout_ts, wait);循环中直接使用
timeout_ts调用pthread_cond_timedwait。处理
pthread_cond_timedwait返回值:result = pthread_cond_timedwait(&p->condvar, &p->mutex, &timeout_ts); if (result == ETIMEDOUT) { // 超时退出 goto cleanup_exit; } else if (result != 0) { // 其他错误处理 goto cleanup_exit; }修复
timespec_diff函数:
确保正确计算时间差,无论纳秒数关系如何:static int64_t timespec_diff(const struct timespec *t1, const struct timespec *t2) { int64_t sec_diff = (int64_t)t1->tv_sec - (int64_t)t2->tv_sec; int64_t nsec_diff = (int64_t)t1->tv_nsec - (int64_t)t2->tv_nsec; return sec_diff * 1000000000LL + nsec_diff; }返回值为
t1 - t2的纳秒差,正数表示t1晚于t2。修正参数类型匹配问题:
将wait参数改为int类型,确保与SEMAPHORE_MAXWAIT、SEMAPHORE_NOWAIT的类型一致,避免switch分支判断错误。
内容的提问来源于stack exchange,提问作者0___________

