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

C语言父子进程TAILQ队列同步异常问题咨询

问题原因

fork()创建子进程后,父子进程拥有完全独立的虚拟地址空间——哪怕fork前初始化了TAILQ队列,fork后双方的队列只是各自地址空间里的副本。子进程往自己的队列插入消息,父进程的队列不会有任何变化,这就是父进程拿不到消息、无输出的根本原因。

解决方案:共享内存+同步锁实现跨进程队列共享

要让父子进程共享TAILQ队列,必须把队列结构、消息数据都放到共享内存中(让双方访问同一块物理内存),同时用进程间同步锁保护队列操作,避免并发修改破坏队列结构。

修正后的代码示例

#define _SVID_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/queue.h>
#include <sys/mman.h>
#include <pthread.h>
#include <errno.h>

// 消息结构体
struct message
{
    int num;
    char status;
    TAILQ_ENTRY(message) queue_entry;
};

// 共享内存中的总结构:包含队列头、同步锁、预分配消息池
#define MAX_MESSAGES 100
struct shared_data
{
    TAILQ_HEAD(head_t, message) head;
    pthread_mutex_t mutex;
    struct message msg_pool[MAX_MESSAGES]; // 预分配消息,避免私有内存分配问题
    int free_msg_count;                    // 空闲消息数量
};

int main()
{
    // 创建匿名共享内存,父子进程自动共享
    struct shared_data *shm_data = mmap(NULL, sizeof(struct shared_data), 
                                       PROT_READ | PROT_WRITE, 
                                       MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (shm_data == MAP_FAILED)
    {
        perror("mmap failed");
        exit(EXIT_FAILURE);
    }

    // 初始化共享资源(仅父进程在fork前执行)
    TAILQ_INIT(&shm_data->head);
    shm_data->free_msg_count = MAX_MESSAGES;

    // 设置互斥锁为进程共享属性,让父子进程都能使用
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
    if (pthread_mutex_init(&shm_data->mutex, &attr) != 0)
    {
        perror("pthread_mutex_init failed");
        exit(EXIT_FAILURE);
    }
    pthread_mutexattr_destroy(&attr);

    pid_t pid = fork();
    if (pid == -1)
    {
        perror("fork failed");
        exit(EXIT_FAILURE);
    }

    if (!pid) // 子进程:循环生产消息
    {
        while (1)
        {
            pthread_mutex_lock(&shm_data->mutex);
            // 检查消息池是否有空闲
            if (shm_data->free_msg_count <= 0)
            {
                pthread_mutex_unlock(&shm_data->mutex);
                sleep(1);
                continue;
            }
            // 从消息池取一个空闲消息
            struct message *p = NULL;
            for (int i = 0; i < MAX_MESSAGES; i++)
            {
                if (!TAILQ_ENTRY_IS_LINKED(&shm_data->msg_pool[i], queue_entry))
                {
                    p = &shm_data->msg_pool[i];
                    break;
                }
            }
            if (p)
            {
                p->num = 1;
                p->status = 's';
                TAILQ_INSERT_TAIL(&shm_data->head, p, queue_entry);
                shm_data->free_msg_count--;
            }
            pthread_mutex_unlock(&shm_data->mutex);

            sleep(1); // 控制生产频率,方便观察输出
        }
    }
    else // 父进程:循环消费消息
    {
        while (1)
        {
            struct message *p = NULL;

            pthread_mutex_lock(&shm_data->mutex);
            p = TAILQ_FIRST(&shm_data->head);
            if (p)
            {
                TAILQ_REMOVE(&shm_data->head, p, queue_entry);
                shm_data->free_msg_count++;
            }
            pthread_mutex_unlock(&shm_data->mutex);

            if (p)
            {
                printf("Num %d and status %c\n", p->num, p->status);
            }
            sleep(1);
        }
    }

    // 清理代码(死循环不会执行,实际退出时需调用)
    pthread_mutex_destroy(&shm_data->mutex);
    munmap(shm_data, sizeof(struct shared_data));

    return 0;
}

关键细节说明

  1. 共享内存:用mmap创建匿名共享内存(MAP_SHARED | MAP_ANONYMOUS),无需手动管理共享内存键,父子进程自动共享同一块物理内存区域。
  2. 预分配消息池:消息结构体直接放在共享内存的消息池中,避免子进程用malloc分配私有内存(父进程无法访问私有内存)的问题。
  3. 进程共享锁:通过pthread_mutexattr_setpshared将互斥锁设置为进程共享属性,确保父子进程能通过同一把锁同步队列操作,防止并发修改破坏TAILQ的链表结构。
  4. 锁保护范围:所有对队列、消息池的读写操作都在加锁后执行,操作完成立即解锁,减少锁竞争时间。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 01:54:52