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

C语言多线程场景出现死锁问题,求排查根源

你的多线程死锁问题排查与修复方案

嘿,我帮你梳理下代码里的核心问题——多线程数量一多就死锁,主要是条件变量使用不当,还有几个线程安全的小坑,咱们逐个解决:

1. 条件变量只唤醒一个线程,导致其余线程永久阻塞

你在READ命令里用了pthread_cond_signal(&read_cond),这个函数只会叫醒一个等着的线程。当线程数超过1时,剩下的线程会一直卡在pthread_cond_wait那里睡大觉,而主线程又在pthread_join等所有线程结束,两边互相等,这不就死锁了嘛!

怎么改:把pthread_cond_signal换成pthread_cond_broadcast(&read_cond),这个会把所有等着的线程都叫醒,保证每个线程都能继续执行。

2. 全局变量present_file搞混了文件名

你创建线程后立刻给present_file加1,但线程跑起来的时候,这个全局变量已经变了!比如第一个线程刚创建,present_file就变成2了,线程执行时会打开head2而不是head1,完全不符合你的预期。

怎么改:别用全局变量判断文件名,给每个线程传它的编号(比如线程的索引),在线程里自己生成文件名:

// 线程里接收索引
int thread_idx = *((int *)args);
char filename[20];
// 直接生成head1、head2...
snprintf(filename, sizeof(filename), "head%d", thread_idx + 1);
FILE *file_ptr = fopen(filename, "wb");

主线程创建线程时,把当前的索引(创建前的present_file值)传进去,别传&offset(这个还有别的问题)。

3. 线程参数传递踩了内存覆盖的坑

你给线程传的是(void *)&offset,这是主线程里的局部变量,下一次执行HEAD命令时,offset会被新值覆盖,之前的线程拿到的偏移量就错了!

怎么改:用你已经定义的OFFSET[100]数组存每个线程的偏移量,同时传递线程的索引:

// 主线程HEAD分支里
OFFSET[present_file] = offset;
// 传递当前的present_file作为线程索引
pthread_create(THREADS + present_file, NULL, tape_reader, (void *)&present_file);
// 再递增索引
present_file++;
n_threads++;

线程里通过索引拿到自己的偏移量:int offset = OFFSET[thread_idx];

4. read变量的线程安全问题

全局变量read的修改和读取没加锁,主线程设read=1时,线程可能因为CPU缓存的原因看不到这个变化,一直卡在while(read == 0)的循环里。

怎么改:修改和读取read时都要加read_mutex锁:

// 主线程READ分支
pthread_mutex_lock(&read_mutex);
read = 1;
pthread_mutex_unlock(&read_mutex);
pthread_cond_broadcast(&read_cond);
// 线程里的等待循环
pthread_mutex_lock(&read_mutex);
while(read == 0){
    pthread_cond_wait(&read_cond, &read_mutex);
}
pthread_mutex_unlock(&read_mutex);

注意:pthread_cond_wait会自动释放锁,被唤醒后重新拿锁,所以外层必须先加锁才行。

5. 其他小问题优化

  • 你原来的读循环里if(i == file_length){i=0;}逻辑有问题,当read_value大于文件长度时,fread到末尾会返回0,这时候buffer的值不会更新,应该检查fread的返回值,返回0时重新rewind文件。
  • file_read锁的范围可以不用那么大,但如果要保证文件读取的顺序,当前的锁逻辑是没问题的。

修复后的核心代码片段

线程函数修改

void* tape_reader(void* args) {
    int thread_idx = *((int *) args);
    int offset = OFFSET[thread_idx];
    char filename[20];
    snprintf(filename, sizeof(filename), "head%d", thread_idx + 1);
    FILE *file_ptr = fopen(filename, "wb");
    if (!file_ptr) {
        perror("Failed to open output file");
        pthread_exit(NULL);
    }

    // 等待READ命令触发
    pthread_mutex_lock(&read_mutex);
    while (read == 0) {
        pthread_cond_wait(&read_cond, &read_mutex);
    }
    pthread_mutex_unlock(&read_mutex);

    // 读取源文件并写入目标文件
    pthread_mutex_lock(&file_read);
    BYTE buffer;
    rewind(file);
    if (fseek(file, offset, SEEK_SET) != 0) {
        perror("Failed to seek in source file");
        fclose(file_ptr);
        pthread_mutex_unlock(&file_read);
        pthread_exit(NULL);
    }
    int bytes_written = 0;
    while (bytes_written < read_value) {
        size_t read_bytes = fread(&buffer, 1, sizeof(buffer), file);
        if (read_bytes == 0) {
            // 读到文件末尾,重新回到开头
            rewind(file);
            continue;
        }
        fwrite(&buffer, 1, read_bytes, file_ptr);
        bytes_written += read_bytes;
    }
    fclose(file_ptr);
    pthread_mutex_unlock(&file_read);

    pthread_exit(NULL);
}

主线程HEAD分支修改

if(strcmp(userInput, "HEAD") == 0){
    scanf("%d", &offset);
    OFFSET[present_file] = offset;
    // 传递当前的索引给线程
    pthread_create(THREADS + present_file, NULL, tape_reader, (void *) &present_file);
    // 打印正确的HEAD编号
    if(offset >= 0){
        printf("HEAD %d at +%d\n", present_file + 1, offset);
    } else{
        printf("HEAD %d at %d\n", present_file + 1, offset);
    }
    printf("\n");
    present_file++;
    n_threads++;
}

主线程READ分支修改

if(strcmp(userInput, "READ") == 0){
    int read_for;
    scanf("%d", &read_for);
    read_value = read_for;

    // 安全修改read变量并唤醒所有线程
    pthread_mutex_lock(&read_mutex);
    read = 1;
    pthread_mutex_unlock(&read_mutex);
    pthread_cond_broadcast(&read_cond);

    printf("Waiting for all threads to finish...\n");
    for(int i = 0; i < n_threads; i++){
        pthread_join(THREADS[i], NULL);
    }
    printf("Finished Reading\n\n");
    fclose(file);
    return 0;
}

这样改完后,不管多少个线程都不会死锁了,文件名和偏移量也能正确对应,线程同步的问题也都解决了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:49:49