为何pthread_cond_signal无法立即唤醒阻塞线程?
多线程GC线程无法被条件变量信号立即唤醒的问题
我正在编写一个多线程程序,辅助GC线程会在数据结构元素数量达标时触发运行。相关核心代码如下:
GC线程处理函数:
void* gc_thread_handler(void *arg) { while (!done) { pthread_mutex_lock(&(gc.mutex)); while (!gc.collect && !done) { pthread_cond_wait(&(gc.cond), &(gc.mutex)); } pthread_mutex_unlock(&(gc.mutex)); rcgc_activate(); } return NULL; }
等待线程会被gc_alloc函数发送的信号唤醒:
void * gc_alloc(size_t sz) { pthread_mutex_lock(&(gc.mutex)); // ... gc.num_chunks++; if (gc.num_chunks > MAX_CHUNKS) { gc.collect = true; pthread_cond_signal(&(gc.cond)); } pthread_mutex_unlock(&(gc.mutex)); return ptr; }
但奇怪的是,信号无法立即唤醒休眠的线程。由于gc_alloc会被反复调用,信号也被多次发送,最终线程确实会唤醒并执行rcgc_activate,但我搞不懂为什么不能立刻响应信号。
以下是最小可复现示例(MRE):
gc.h
#ifndef RCGC_H #define RCGC_H #define RCGC_OWALLOC(ptr, sz) (rcgc_owalloc((void **) (ptr), (sz))); #define RCGC_ASSIGN(dest, src) (rcgc_assign((void **) (dest), (void **) (src))); #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <pthread.h> struct rcgc_allocation { void *data; struct rcgc_allocation *next; size_t counter; bool is_free; }; // 引用计数GC struct rcgc { struct rcgc_allocation *head; struct rcgc_allocation *tail; int num_chunks; bool collect; pthread_mutex_t mutex; pthread_cond_t cond; }; void rcgc_init(void); void *rcgc_alloc(size_t sz); void *rcgc_owalloc(void **dest, size_t sz); void rcgc_assign(void **dest, void **src); void rcgc_activate(void); #endif // RCGC_H
gc.c
#include "rcgc.h" #define MAX_CHUNKS 1000 struct rcgc gc; static struct rcgc_allocation *rcgc_search(void *ptr); void rcgc_init(void) { pthread_mutex_init(&(gc.mutex), NULL); pthread_cond_init(&(gc.cond), NULL); gc.head = gc.tail = NULL; gc.num_chunks = 0; gc.collect = false; } void * rcgc_alloc(size_t sz) { pthread_mutex_lock(&(gc.mutex)); struct rcgc_allocation *node = calloc(1, sizeof(struct rcgc_allocation)); // 添加到链表 if (gc.head == NULL) { gc.head = gc.tail = node; gc.head->next = gc.tail->next = NULL; } else { gc.tail->next = node; gc.tail = node; } // 分配数据内存 void *ptr = calloc(1, sz); node->data = ptr; node->is_free = false; node->counter = 1; // 分配数量达标时发送信号 gc.num_chunks++; if (gc.num_chunks > MAX_CHUNKS && !gc.collect) { gc.collect = true; pthread_cond_signal(&(gc.cond)); } pthread_mutex_unlock(&(gc.mutex)); return ptr; } void * rcgc_owalloc(void **dest, size_t sz) { pthread_mutex_lock(&(gc.mutex)); if (dest == NULL) { exit(EXIT_FAILURE); } else { struct rcgc_allocation *old_node = rcgc_search(*dest); if (old_node != NULL) { old_node->counter--; } pthread_mutex_unlock(&(gc.mutex)); return rcgc_alloc(sz); } } void rcgc_assign(void **dest, void **src) { pthread_mutex_lock(&(gc.mutex)); // 目标已有分配则减少引用计数 if (dest != NULL) { struct rcgc_allocation *dest_alloc = rcgc_search(*dest); if (dest_alloc != NULL) { dest_alloc->counter--; } } // 增加源的引用计数 struct rcgc_allocation *src_alloc = rcgc_search(*src); src_alloc->counter++; *dest = *src; pthread_mutex_unlock(&(gc.mutex)); } void rcgc_activate(void) { pthread_mutex_lock(&(gc.mutex)); for (struct rcgc_allocation *curr = gc.head; curr != NULL; curr = curr->next) { if (curr->counter == 0 && !curr->is_free) { free(curr->data); curr->data = NULL; curr->is_free = true; gc.num_chunks--; } } gc.collect = false; pthread_mutex_unlock(&(gc.mutex)); } static struct rcgc_allocation * rcgc_search(void *ptr) { for (struct rcgc_allocation *curr = gc.head; curr != NULL; curr = curr->next) { if (ptr == curr->data) { return curr; } } return NULL; }
main.c
#include <stdio.h> #include <stdlib.h> #include "rcgc.h" extern struct rcgc gc; static bool done = false; void* rcgc_thread_handler(void *arg) { while (!done) { pthread_mutex_lock(&(gc.mutex)); while (!gc.collect && !done) { pthread_cond_wait(&(gc.cond), &(gc.mutex)); } pthread_mutex_unlock(&(gc.mutex)); rcgc_activate(); } return NULL; } int main(void) { rcgc_init(); pthread_t pid; pthread_create(&pid, NULL, rcgc_thread_handler, NULL); // 创建测试用分配与引用操作 int *arr1 = rcgc_alloc(sizeof(int) * 10); int *arr2 = NULL; // 持续执行分配操作 double val = 0; while (val < 1) { val += 0.0001; arr1 = RCGC_OWALLOC(&arr1, sizeof(int)); RCGC_ASSIGN(&arr2, &arr1); } done = true; pthread_cond_broadcast(&(gc.cond)); pthread_join(pid, NULL); return 0; }
问题原因分析
核心问题是主线程持续高频占用互斥锁,导致GC线程被唤醒后无法立即获取锁执行逻辑:
- 当
gc.num_chunks超过阈值时,gc_alloc设置gc.collect=true并发送信号,GC线程被唤醒。 - 但主线程的循环一直在调用
RCGC_OWALLOC和RCGC_ASSIGN,这两个函数都会频繁加锁、解锁,导致锁大部分时间被主线程持有。 - GC线程唤醒后首先尝试获取
gc.mutex,但锁被主线程占用,只能进入等待状态,直到主线程某次释放锁后,GC线程才能拿到锁并执行rcgc_activate。
另外还有一个潜在问题:done变量未加volatile修饰,可能被编译器优化,导致GC线程无法及时感知done的更新。
修复方案
- 降低主线程锁占用频率:在主线程的循环中加入短暂休眠,主动让渡CPU给GC线程获取锁的机会:
// main函数的循环中添加 val += 0.0001; arr1 = RCGC_OWALLOC(&arr1, sizeof(int)); RCGC_ASSIGN(&arr2, &arr1); usleep(1); // 让渡CPU时间片
- 确保
done变量的可见性:给done添加volatile修饰,避免编译器优化:
static volatile bool done = false;
- 改用
pthread_cond_broadcast提升鲁棒性:虽然当前只有一个等待线程,但广播能确保信号不会因线程状态变化丢失:
// 在gc_alloc中替换信号发送逻辑 pthread_cond_broadcast(&(gc.cond));
内容的提问来源于stack exchange,提问作者TheProgrammer
相关产品推荐
相关产品推荐

