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

OS课程作业:5个分离线程互斥同步异常求助

问题描述

这是操作系统课程的学术练习,要求仅使用mutex锁与解锁同步5个分离线程,禁止强制线程串行执行,线程启动后除mutex外不受外部影响,父线程不能调用pthread_join。

当前代码创建5个分离线程,用于读取文件并更新全局变量,但仅3个线程输出ID,且没有线程进入while循环;若在main的线程创建循环中取消sleep(1)的注释可得到预期输出,但这属于强制串行,不符合要求。

输出结果

thread: 6156515168
thread: 6156515192
thread: 6156515176

原代码

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <pthread.h>

pthread_mutex_t mutex_lock = PTHREAD_MUTEX_INITIALIZER; // declaring mutex
int FileNameHelper=1;
int balance=0;

void* detatchedThread(void *param){
    long tid = (long)param;
    char* base = "data";
    char filename[256];
    char buf[100];
    sprintf(filename, "%s%d.in", base, FileNameHelper); //creates data1.in, data2.in... 
    FileNameHelper ++;
    FILE *inputFile = fopen(filename, "r");
    printf ("thread: %ld\n", tid);

    // critical sec line
    if(fgets(buf, sizeof buf, inputFile) == NULL)
        return NULL; // could not read first line
    sleep(1); // make sure each thread runs long enough to get the random update behavior required.

    pthread_mutex_lock(&mutex_lock); //we are in the critical section, lock mutex
    while(fgets(buf, sizeof buf, inputFile) != NULL) {
        int val;
        if(sscanf(buf, "%d", &val) != 1){
            break;
        }
        printf("%d\n", val);
        balance += val;
        printf ("Account balance after thread %ld is $%d\n", tid, balance);
    }
    pthread_mutex_unlock(&mutex_lock);

    if(buf[0] != 'W')
        return NULL;// last line data was invalid
    
    pthread_exit(NULL);
}

int main(){
    pthread_t th[5];
    //initialize the mutex
    if(pthread_mutex_init(&mutex_lock, NULL) != 0){
        printf("\nmutex init has failed\n");
        return 1;
    }

    //call the 5 threads, Detach the threads once they are created.
    for (int i = 0; i < 5; i++){
        pthread_create(&th[i], NULL, detatchedThread, (void *)&th[i]);
        pthread_detach(th[i]); 
        //sleep(1); uncommenting this line gives me the expected behavior
    }
    
    pthread_mutex_destroy(&mutex_lock);
    return 0;
}
问题分析与解决建议

核心问题点

  1. 主线程提前退出并销毁mutex:main函数创建完线程后立即销毁mutex并返回,此时大部分线程还未执行完成,进程直接终止导致未执行的线程被强制结束;同时mutex被销毁后,线程调用pthread_mutex_lock会触发未定义行为。
  2. 共享变量FileNameHelper无同步保护:多个线程同时读写该全局变量,会出现竞态条件,导致多个线程获取到相同的文件编号,打开同一个文件甚至打开失败,后续fgets直接返回NULL使线程提前退出,无法进入while循环。
  3. 线程参数传递错误:创建线程时传递&th[i],但循环中i的值会持续变化,线程启动后可能读取到错误的tid值,甚至是非法内存地址。
  4. 未检查文件打开结果:fopen可能失败(比如文件不存在),后续直接使用inputFile调用fgets会导致崩溃或未定义行为。

修复方案

  1. 主线程等待所有线程完成:新增全局计数器completed_threads,每个线程结束前加锁更新计数器;主线程循环加锁检查计数器是否达到5,直到所有线程完成再销毁mutex并退出。
  2. 同步FileNameHelper的访问:修改FileNameHelper时必须加mutex锁,避免竞态条件。
  3. 修复线程参数传递:直接传递(void *)(long)i作为线程ID(因为int和long在多数平台下可以安全转换),避免指针引用循环变量的问题。
  4. 增加文件打开错误检查:判断fopen的返回值,若失败则直接退出线程。
  5. 调整sleep位置(可选):将sleep(1)移到mutex锁外,不影响同步逻辑的同时保留随机执行的效果。

修改后的代码示例

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <pthread.h>

pthread_mutex_t mutex_lock = PTHREAD_MUTEX_INITIALIZER;
int FileNameHelper = 1;
int balance = 0;
int completed_threads = 0; // 新增:记录已完成的线程数

void* detatchedThread(void *param){
    long tid = (long)param;
    char base[] = "data";
    char filename[256];
    char buf[100];
    FILE *inputFile = NULL;

    // 同步获取文件编号
    pthread_mutex_lock(&mutex_lock);
    int file_num = FileNameHelper++;
    pthread_mutex_unlock(&mutex_lock);

    sprintf(filename, "%s%d.in", base, file_num);
    inputFile = fopen(filename, "r");
    // 检查文件是否打开成功
    if (!inputFile) {
        printf("thread %ld: failed to open file %s\n", tid, filename);
        goto thread_cleanup;
    }

    printf("thread: %ld\n", tid);

    // 读取第一行,检查是否成功
    if(fgets(buf, sizeof(buf), inputFile) == NULL) {
        printf("thread %ld: failed to read first line of %s\n", tid, filename);
        goto thread_cleanup;
    }

    sleep(1); // 保留随机执行效果

    // 进入临界区更新balance
    pthread_mutex_lock(&mutex_lock);
    while(fgets(buf, sizeof(buf), inputFile) != NULL) {
        int val;
        if(sscanf(buf, "%d", &val) != 1){
            break;
        }
        printf("%d\n", val);
        balance += val;
        printf("Account balance after thread %ld is $%d\n", tid, balance);
    }
    pthread_mutex_unlock(&mutex_lock);

    // 检查最后一行是否有效
    if(buf[0] != 'W') {
        printf("thread %ld: last line invalid\n", tid);
    }

thread_cleanup:
    if (inputFile) {
        fclose(inputFile);
    }
    // 更新完成线程数
    pthread_mutex_lock(&mutex_lock);
    completed_threads++;
    pthread_mutex_unlock(&mutex_lock);

    pthread_exit(NULL);
}

int main(){
    pthread_t th[5];
    if(pthread_mutex_init(&mutex_lock, NULL) != 0){
        printf("mutex init has failed\n");
        return 1;
    }

    for (int i = 0; i < 5; i++){
        // 传递i的副本作为线程ID
        if(pthread_create(&th[i], NULL, detatchedThread, (void *)(long)i) != 0) {
            printf("failed to create thread %d\n", i);
            return 1;
        }
        pthread_detach(th[i]);
    }

    // 等待所有线程完成
    while(1) {
        pthread_mutex_lock(&mutex_lock);
        if (completed_threads >= 5) {
            pthread_mutex_unlock(&mutex_lock);
            break;
        }
        pthread_mutex_unlock(&mutex_lock);
        usleep(100000); // 避免空循环占用CPU
    }

    pthread_mutex_destroy(&mutex_lock);
    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 23:40:42