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

为何互斥锁(mutex_lock)会导致段错误?(哲学家就餐问题)

哲学家就餐问题段错误修复方案

核心错误分析

  1. Fork指针赋值错误:main函数中初始化data数组时,错误使用data->left_fork而非data[i].left_fork,导致仅第一个哲学家的fork指针被正确赋值,其余哲学家的fork指针为野指针,调用pthread_mutex_lock时触发段错误。
  2. 死亡标志未共享:每个t_data结构体单独维护dead变量,monitor线程标记某哲学家死亡后,其他哲学家无法感知该状态,会继续执行。
  3. 初始饥饿判断逻辑错误:last_eaten初始值为0,程序启动后monitor会直接判定哲学家饿死,需初始化为模拟开始时间。
  4. 无输出互斥:多线程同时调用printf会导致输出混乱,需添加全局输出mutex。

修复后的完整代码

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/time.h>

typedef struct s_philo
{
    int                 n_philo;        // 哲学家数量
    int                 time_to_die;    // 饿死时间(ms)
    int                 time_to_eat;    // 进食时间(ms)
    int                 time_to_sleep;  // 睡眠时间(ms)
    int                 need_eat;       // 每个哲学家需进食次数
    u_int64_t           start_time;     // 模拟启动时间
    int                 dead;           // 全局死亡标志(0=存活,1=死亡)
    pthread_mutex_t     print_mutex;    // 输出互斥锁
}   t_philo;

typedef struct s_data
{
    pthread_mutex_t     *left_fork;     // 左叉子指针
    pthread_mutex_t     *right_fork;    // 右叉子指针
    u_int64_t           last_eaten;     // 最后一次进食时间
    t_philo             *philo;         // 全局参数结构体指针
    int                 id;             // 哲学家ID
    int                 is_eating;      // 是否正在进食
    int                 n_eat;          // 已进食次数
}   t_data;

u_int64_t   get_time(void);
int         ft_usleep(useconds_t time);
int         ft_atoi(const char *str);
void        init_philo(t_philo *philo, int argc, char **argv);
void        init_data(t_data *data, t_philo *philo, int id, pthread_mutex_t *forks);
void        *routine(void *vdata);
void        *monitor(void *vdata);
int         ft_eat(void *vdata);
int         ft_sleep(void *vdata);
int         ft_think(void *vdata);

int main(int argc, char **argv)
{
    t_philo         philo;
    t_data          *data;
    pthread_t       *philos;
    pthread_t       monitoring;
    pthread_mutex_t *forks;
    int             i;
    int             n_philo;

    if (argc < 5 || argc > 6)
    {
        printf("Usage: %s <n_philo> <time_to_die> <time_to_eat> <time_to_sleep> [need_eat]\n", argv[0]);
        return 1;
    }
    n_philo = ft_atoi(argv[1]);
    data = malloc(sizeof(t_data) * n_philo);
    philos = malloc(sizeof(pthread_t) * n_philo);
    forks = malloc(sizeof(pthread_mutex_t) * n_philo);
    if (!data || !philos || !forks)
    {
        perror("malloc failed");
        return 1;
    }

    init_philo(&philo, argc, argv);
    pthread_mutex_init(&philo.print_mutex, NULL);
    i = 0;
    while (i < philo.n_philo)
    {
        pthread_mutex_init(&forks[i], NULL);
        i++;
    }
    i = 0;
    while(i < philo.n_philo)
    {
        init_data(&data[i], &philo, i, forks);
        i++;
    }
    i = 0;
    philo.start_time = get_time();
    pthread_create(&monitoring, NULL, &monitor, data);
    while (i < philo.n_philo)
    {
        pthread_create(&philos[i], NULL, &routine, &data[i]);
        i++;
    }
    i = 0;
    while (i < philo.n_philo)
    {
        pthread_join(philos[i], NULL);
        pthread_mutex_destroy(&forks[i]);
        i++;
    }
    pthread_join(monitoring, NULL);
    pthread_mutex_destroy(&philo.print_mutex);
    free(data);
    free(philos);
    free(forks);
    return (0);
}

void    *monitor(void *vdata)
{
    t_data      *data;
    int         i;
    t_philo     *philo;

    data = (t_data *)vdata;
    philo = data[0].philo;
    while (!philo->dead)
    {
        i = 0;
        while(i < philo->n_philo)
        {
            pthread_mutex_lock(&philo->print_mutex);
            if (get_time() - data[i].last_eaten > (u_int64_t)philo->time_to_die && !data[i].is_eating)
            {
                printf("%llu %d died\n", get_time() - philo->start_time, data[i].id + 1);
                philo->dead = 1;
                pthread_mutex_unlock(&philo->print_mutex);
                return NULL;
            }
            // 检查是否所有哲学家都完成进食次数
            if (philo->need_eat != -1)
            {
                int all_finished = 1;
                for (int j = 0; j < philo->n_philo; j++)
                {
                    if (data[j].n_eat < philo->need_eat)
                    {
                        all_finished = 0;
                        break;
                    }
                }
                if (all_finished)
                {
                    philo->dead = 1;
                    pthread_mutex_unlock(&philo->print_mutex);
                    return NULL;
                }
            }
            pthread_mutex_unlock(&philo->print_mutex);
            i++;
        }
        usleep(1000); // 减少轮询频率,降低CPU占用
    }
    return (NULL);
}

void    *routine(void *vdata)
{
    t_data  *data;
    t_philo *philo;

    data = (t_data *)vdata;
    philo = data->philo;
    // 偶数ID哲学家先等待,避免同时抢叉子导致死锁
    if (data->id % 2 == 0)
        ft_usleep(100);
    while (!philo->dead)
    {
        if (ft_eat(data) != 0)
            break;
        if (philo->need_eat != -1 && data->n_eat >= philo->need_eat)
            break;
        ft_sleep(data);
        ft_think(data);
    }
    return (NULL);
}

int ft_sleep(void *vdata)
{
    t_data      *data;
    t_philo     *philo;

    data = (t_data *)vdata;
    philo = data->philo;
    pthread_mutex_lock(&philo->print_mutex);
    printf("%llu %d is sleeping\n", get_time() - philo->start_time, data->id + 1);
    pthread_mutex_unlock(&philo->print_mutex);
    ft_usleep(philo->time_to_sleep);
    return (0);
}

int ft_eat(void *vdata)
{
    t_data  *data;
    t_philo *philo;

    data = (t_data *)vdata;
    philo = data->philo;
    // 避免死锁:奇数ID先拿右叉子,偶数ID先拿左叉子
    if (data->id % 2 == 1)
    {
        pthread_mutex_lock(data->right_fork);
        pthread_mutex_lock(data->left_fork);
    }
    else
    {
        pthread_mutex_lock(data->left_fork);
        pthread_mutex_lock(data->right_fork);
    }

    pthread_mutex_lock(&philo->print_mutex);
    printf("%llu %d has taken a fork\n", get_time() - philo->start_time, data->id + 1);
    printf("%llu %d has taken a fork\n", get_time() - philo->start_time, data->id + 1);
    printf("%llu %d is eating\n", get_time() - philo->start_time, data->id + 1);
    pthread_mutex_unlock(&philo->print_mutex);

    data->is_eating = 1;
    data->last_eaten = get_time();
    ft_usleep(philo->time_to_eat);
    data->n_eat++;
    data->is_eating = 0;

    pthread_mutex_unlock(data->left_fork);
    pthread_mutex_unlock(data->right_fork);
    return (0);
}

int ft_think(void *vdata)
{
    t_data      *data;
    t_philo     *philo;

    data = (t_data *)vdata;
    philo = data->philo;
    pthread_mutex_lock(&philo->print_mutex);
    printf("%llu %d is thinking\n", get_time() - philo->start_time, data->id + 1);
    pthread_mutex_unlock(&philo->print_mutex);
    return (0);
}

int ft_usleep(useconds_t time)
{
    u_int64_t  start;
    start = get_time();
    while ((get_time() - start) < time)
        usleep(100); // 小粒度睡眠,提高精度
    return(0);
}

u_int64_t   get_time(void)
{
    struct timeval  tv;
    
    gettimeofday(&tv, NULL);
    return ((tv.tv_sec * (u_int64_t)1000) + (tv.tv_usec / 1000));
}

void    init_philo(t_philo *philo, int argc, char **argv)
{
    philo->n_philo = ft_atoi(argv[1]);
    philo->time_to_die = ft_atoi(argv[2]);
    philo->time_to_eat = ft_atoi(argv[3]);
    philo->time_to_sleep = ft_atoi(argv[4]);
    if (argc == 6)
        philo->need_eat = ft_atoi(argv[5]);
    else
        philo->need_eat = -1;
    philo->dead = 0;
}

void    init_data(t_data *data, t_philo *philo, int id, pthread_mutex_t *forks)
{
    data->id = id;
    data->last_eaten = philo->start_time; // 初始化为启动时间,避免初始饥饿判定错误
    data->is_eating = 0;
    data->n_eat = 0;
    data->philo = philo;
    data->left_fork = &forks[id];
    if (id == philo->n_philo - 1)
        data->right_fork = &forks[0];
    else
        data->right_fork = &forks[id + 1];
}

// 实现ft_atoi
int ft_atoi(const char *str)
{
    int res = 0;
    int sign = 1;
    while (*str == ' ')
        str++;
    if (*str == '-')
    {
        sign = -1;
        str++;
    }
    else if (*str == '+')
        str++;
    while (*str >= '0' && *str <= '9')
    {
        res = res * 10 + (*str - '0');
        str++;
    }
    return res * sign;
}

关键修复点说明

  • 修正Fork指针赋值:在init_data中正确为每个哲学家的left_fork和right_fork赋值,避免野指针。
  • 共享死亡标志:将dead变量移至t_philo结构体,所有线程共享该状态,monitor标记死亡后所有哲学家线程会终止。
  • 初始化last_eaten:将last_eaten初始化为模拟启动时间,避免程序刚启动就判定哲学家饿死。
  • 添加输出互斥:新增print_mutex,所有printf操作前加锁,避免多线程输出混乱。
  • 死锁避免:偶数ID哲学家先拿左叉子,奇数ID先拿右叉子,打破循环等待条件,避免死锁。
  • 动态内存分配:改用malloc分配数组,避免栈溢出(当哲学家数量较多时)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 08:17:01