为何互斥锁(mutex_lock)会导致段错误?(哲学家就餐问题)
哲学家就餐问题段错误修复方案
核心错误分析
- Fork指针赋值错误:main函数中初始化data数组时,错误使用
data->left_fork而非data[i].left_fork,导致仅第一个哲学家的fork指针被正确赋值,其余哲学家的fork指针为野指针,调用pthread_mutex_lock时触发段错误。 - 死亡标志未共享:每个
t_data结构体单独维护dead变量,monitor线程标记某哲学家死亡后,其他哲学家无法感知该状态,会继续执行。 - 初始饥饿判断逻辑错误:
last_eaten初始值为0,程序启动后monitor会直接判定哲学家饿死,需初始化为模拟开始时间。 - 无输出互斥:多线程同时调用
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
相关产品推荐
相关产品推荐

