You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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线程被唤醒后无法立即获取锁执行逻辑:

  1. 当gc.num_chunks超过阈值时,gc_alloc设置gc.collect=true并发送信号,GC线程被唤醒。
  2. 但主线程的循环一直在调用RCGC_OWALLOC和RCGC_ASSIGN,这两个函数都会频繁加锁、解锁,导致锁大部分时间被主线程持有。
  3. GC线程唤醒后首先尝试获取gc.mutex,但锁被主线程占用,只能进入等待状态,直到主线程某次释放锁后,GC线程才能拿到锁并执行rcgc_activate。

另外还有一个潜在问题:done变量未加volatile修饰,可能被编译器优化,导致GC线程无法及时感知done的更新。

修复方案

  1. 降低主线程锁占用频率:在主线程的循环中加入短暂休眠,主动让渡CPU给GC线程获取锁的机会:
// main函数的循环中添加
val += 0.0001;
arr1 = RCGC_OWALLOC(&arr1, sizeof(int));
RCGC_ASSIGN(&arr2, &arr1);
usleep(1); // 让渡CPU时间片
  1. 确保done变量的可见性:给done添加volatile修饰,避免编译器优化:
static volatile bool done = false;
  1. 改用pthread_cond_broadcast提升鲁棒性:虽然当前只有一个等待线程,但广播能确保信号不会因线程状态变化丢失:
// 在gc_alloc中替换信号发送逻辑
pthread_cond_broadcast(&(gc.cond));

内容的提问来源于stack exchange,提问作者TheProgrammer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 01:53:13