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

基于Pthreads的程序在macOS崩溃但Linux正常的原因咨询

多线程程序在macOS崩溃但Linux正常的原因分析

问题背景

我有一个基于Pthreads的多线程程序,包含生产者、消费者、打印三个线程:

  • 生产者线程往链表中添加元素,直到链表元素数达到10
  • 消费者线程从链表头部删除元素,直到链表为空
  • 打印线程持续遍历并打印链表内容

程序在macOS(Darwin Kernel Version 19.6.0)运行几秒后就会崩溃,但在Linux系统中却能正常运行。如果给printer线程的print_list调用前后加上pthread_mutex_lock/unlock,macOS下程序就能正常运行;但Linux下即使不加锁,程序也不会崩溃。我想知道这是Linux线程调度策略不同导致的,还是存在其他原因?

完整程序代码

prod_consume.c

#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include "list.h"

#define RANGE_MIN 1
#define RANGE_MAX 10

pthread_t tid[3];

list_t list;

pthread_mutex_t queue_lock;

/* Return a uniformly random number in the range [low,high]. */
int random_range (unsigned const low, unsigned const high)
{
  unsigned const range = high - low + 1;
  return low + (int) (((double) range) * rand() / (RAND_MAX + 1.0));
}
/*
 * consumer thread function
 */
void* consumer(void *arg)
{
        static int val = 0;
        while (1) {
                //sleep(1);
            pthread_mutex_lock(&queue_lock);
                while (list.total_elem > 0)
                    consume_and_delete(&list);
            pthread_mutex_unlock(&queue_lock);
        }
}

void *producer(void *arg)
{
        while (1) {
                //sleep(1);
            pthread_mutex_lock(&queue_lock);
            while (list.total_elem < 10)
                add_to_list(&list, random_range(RANGE_MIN, RANGE_MAX));
            pthread_mutex_unlock(&queue_lock);
        }
}

/*
 * printer thread function
 */
void* printer(void *arg)
{
        while (1) {
                
        //pthread_mutex_lock(&queue_lock);  //lines deliberately commented out to show the crash
                print_list(&list);
        //pthread_mutex_unlock(&queue_lock);
        }
}

int main(void)
{
    int ret;

        if (pthread_mutex_init(&queue_lock, NULL) != 0) {
                printf("\n mutex init failed\n");
                return 1;
        }


    ret = pthread_create(&(tid[1]), NULL, &consumer, NULL);
    if (ret != 0) {
        printf("\ncan't create thread :[%s]", strerror(ret));
    }
    ret = pthread_create(&(tid[0]), NULL, &producer, NULL);
    if (ret != 0) {
        printf("\ncan't create thread :[%s]", strerror(ret));
    }
    ret = pthread_create(&(tid[2]), NULL, &printer, NULL);
    if (ret != 0) {
        printf("\ncan't create thread :[%s]", strerror(ret));
        }

    pthread_join(tid[0], NULL);
    pthread_join(tid[1], NULL);
    pthread_join(tid[2], NULL);

    pthread_mutex_destroy(&queue_lock);

    return 0;
}

list.h

#ifndef __LIST_H__
#define __LIST_H__

typedef struct node_ {
    int num;
    struct node_ *next;
} node_t;

typedef struct list_ {
    int total_elem;
    node_t *head;
} list_t;

#define TRUE 1
#define FALSE 0

void add_to_list(list_t *list, int num);
node_t *allocate_new(int num);
void consume_and_delete(list_t *list);
void print_list(list_t *list);

#endif

list.c

#include <stdio.h>
#include <stdlib.h>
#include "list.h"

void add_to_list(list_t *list, int val)
{
        node_t *tmp;
        node_t *new;

    new = allocate_new(val);
    if (!new) {
        printf("%s: allocation failure \n", __FUNCTION__);
    }

        printf("%s: Enqueueing %d\n", __FUNCTION__, new->num);

        if (!list) {
                return;
        }

        if (!list->head) {
                list->total_elem++;
                list->head = new;
                return;
        }

        tmp = list->head;
        while (tmp->next) {
                tmp = tmp->next;
        }
        tmp->next = new;
        list->total_elem++;
}
node_t *allocate_new(int num)
{
        node_t *tmp;

        tmp = malloc(sizeof(node_t));
        if (!tmp) {
                printf("%s: failed in malloc\n", __FUNCTION__);
                return NULL;
        }
        tmp->num = num;
        tmp->next = NULL;
        return tmp;
}
/* reads from the front of the queue and deletes the element
 */
void consume_and_delete(list_t *list)
{
        node_t *tmp, *next;

        if (!list->head) {
                return;
        }

        tmp = list->head;
        next = tmp->next;

        printf("%s: Dequeueing %d\n", __FUNCTION__,tmp->num);
        list->head = next;
        list->total_elem--;
        free(tmp);
}
void print_list(list_t *list)
{
        node_t *tmp;

        if (!list->head) {
                printf("%s: queue empty \n", __FUNCTION__);
        }

        tmp = list->head;

        while (tmp) {
                printf("%d ", tmp->num);
                tmp = tmp->next;
        }
        printf("\n");
}

Makefile

all:    prodcon

prodcon:        list.o prod_consume.o
        gcc -g -o prodcon list.o prod_consume.o -lpthread

list.o: list.c list.h
        gcc -g -c -o list.o list.c

prod_consume.o: prod_consume.c list.h
        gcc -g -c -o prod_consume.o prod_consume.c

clean:
        rm -f *.o ./prodcon

原因分析

核心本质:未定义行为的随机性

程序的问题根源是数据竞争:生产者和消费者通过锁同步了链表操作,但打印线程完全没有同步,直接访问共享的链表结构。这种场景下,程序的行为属于C标准定义的未定义行为——未定义行为的结果是完全不可预测的,可能崩溃、可能输出错误结果、也可能“看起来正常”,不同系统或不同运行次的表现都可能不同。Linux下没崩溃只是运气好,不是程序本身正确。

导致跨平台差异的具体因素

  1. 线程调度策略差异

    • macOS的线程调度可能更频繁地切换线程,导致打印线程更大概率在链表修改的中间状态(比如生产者还没完成尾节点的next指针赋值、消费者刚free了节点但打印线程还在访问该节点)时执行,触发空指针访问或野指针访问,直接导致崩溃。
    • Linux默认采用分时调度策略,可能让生产者/消费者线程持有锁的时间更连续,打印线程插入执行的时机刚好避开了链表的不一致状态,从而没触发崩溃。
  2. 编译器与内存模型差异

    • macOS默认用Clang编译,Linux默认用GCC,两者的优化策略、内存访问重排规则不同。比如在macOS下,打印线程可能看到未同步的total_elem和链表指针的矛盾值(比如total_elem显示有元素,但head已经被消费者free);而Linux下可能因为缓存一致性或编译器隐含的内存屏障,刚好没出现这种矛盾。
  3. 链表操作的非原子性

    • 链表的添加、删除操作都是多步非原子操作:
      • add_to_list需要遍历到尾节点、设置next指针、更新total_elem,三步操作之间线程可能切换;
      • consume_and_delete需要修改head、更新total_elem、free节点,同样是多步操作。
        打印线程在这些步骤中间访问链表,就可能拿到“半修改”的无效链表结构,进而触发崩溃。

为什么加锁后都正常

给打印线程加上锁后,所有访问共享链表的线程都通过同一把互斥锁同步,确保打印线程只有在链表处于一致状态时才能访问,彻底消除了数据竞争,所以两个系统都能稳定运行。

结论

Linux下程序没崩溃只是未定义行为的一种表现,不是Linux的线程调度“更安全”,也不是程序本身没有问题。正确的做法是给所有访问共享链表的线程都加上互斥锁同步,才能保证跨平台的正确性和稳定性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 13:01:05