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

启用GCC优化时基于共享内存的多进程求和程序挂死求助

问题:多进程异步求和程序在-O2优化下挂死

我在training.olinfo.it平台完成作业题bigsomma:给定N个整数序列,求其总和,N≤50,000,000,每个整数范围为−67108864至67108863,时间限制1秒,内存限制256MiB。

单进程版本耗时约0.9s,为进一步优化,我用fork创建子进程,通过共享内存双缓冲区实现子进程读数据、父进程计算求和的异步处理。但本地及平台编译时,启用-O2等GCC优化选项后程序挂死,关闭优化则正常运行;而平台默认启用-O2,无法关闭。调试发现进程可能陷入循环等待,部分指针被优化跳过。附上代码请求排查错误并提供解决方案。

#define _SVID_SOURCE

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdbool.h>
#include<sys/wait.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<assert.h>

#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")

#define buf_size 0x10000

void swap(void** a, void** b){
    void* c=*a;
    *a=*b;
    *b=c;
}

long long somma(FILE *__restrict__ f){
    size_t size_read;
    long long sum=0;
    int current=0, neg=0, temp=0;

    //int fd=f->_fileno;
    int fd=fileno(f);
    char c;

    key_t shm_key=ftok("bigsomma_async.c", 65);
    bool *can_proceed;
    bool *acknowledge_read;
    size_t *size_read_ptr;
    char* buf_1;
    char* buf_2;
    char** buf_1_ptr;
    char** buf_2_ptr;

    int shmid=shmget(shm_key, 
                                sizeof(bool)+
                                sizeof(bool)+
                                sizeof(size_t)+
                                sizeof(char)*buf_size+
                                sizeof(char)*buf_size+
                                sizeof(char*)+
                                sizeof(char*)
                            , IPC_CREAT | 0777);
    if (shmid == -1) {
        perror("shmget");
        return 1;
    }

    char* shared_mem=(char*)shmat(shmid, NULL, 0);

    can_proceed=(bool*)shared_mem;
    acknowledge_read=(bool*)(can_proceed+1);
    size_read_ptr=(size_t*)(acknowledge_read+1);
    buf_1=(char*)(size_read_ptr+1);
    buf_2=(char*)(buf_1+buf_size);
    buf_1_ptr=(char**)(buf_2+buf_size);
    buf_2_ptr=(char**)(buf_1_ptr+1);

    *can_proceed=true;
    *acknowledge_read=true;
    *size_read_ptr=1;
    *buf_1_ptr=buf_1;
    *buf_2_ptr=buf_2;

    pid_t pfork=fork();
    if(!pfork){
        while(*size_read_ptr!=0){
            while(!*acknowledge_read){}
            *size_read_ptr=read(fd, buf_2, buf_size);
            
            while(!*can_proceed){}

            /*char* tmp=*buf_1_ptr;
            *buf_1_ptr=*buf_2_ptr;
            *buf_2_ptr=tmp;*/

            swap((void**)buf_1_ptr, (void**)buf_2_ptr);

            *can_proceed=false;
            *acknowledge_read=false;
        }
        exit(0);
    }else{
        int size_read;
        while(*size_read_ptr!=0){
            while(*can_proceed){}

            size_read=*size_read_ptr;
            *acknowledge_read=true;

            for(current=0; current<size_read; current++){
                c=(*buf_1_ptr)[current];
                if(c>='0'){
                    temp*=10;
                    temp+=c-'0';
                }else if(c=='-'){
                    neg=-1;
                }else{
                    sum+=temp*neg;
                    temp=0;
                    neg=1;
                }
            }
            *can_proceed=true;
        }

    }
    wait(NULL);

    shmdt(shared_mem);
    shmctl(shmid, IPC_RMID, NULL);

    return sum+temp*neg;

}

问题根源分析

  • 共享内存同步变量未加volatile修饰:GCC在-O2及以上优化级别会将非volatile变量缓存到寄存器,不会每次从内存读取最新值。can_proceed、acknowledge_read等跨进程同步变量被缓存后,进程无法感知对方的状态变化,直接导致无限循环等待。
  • 手动内存偏移的对齐问题:通过手动计算指针偏移来分配共享内存区域,未考虑不同类型的内存对齐要求。优化后编译器可能调整内存布局,导致指针访问未对齐内存,触发未定义行为。
  • 初始同步状态逻辑冲突:初始设置*can_proceed=true和*acknowledge_read=true,子进程和父进程的等待条件逻辑不匹配,容易引发初始阶段的竞争条件。

解决方案

1. 为同步变量添加volatile修饰

所有跨进程共享的状态变量必须用volatile声明,强制编译器每次从内存读取最新值:

volatile bool *can_proceed;
volatile bool *acknowledge_read;
volatile size_t *size_read_ptr;
volatile char** buf_1_ptr;
volatile char** buf_2_ptr;

同时修改swap函数适配volatile指针:

void swap(volatile void** a, volatile void** b){
    void* c=*a;
    *a=*b;
    *b=c;
}

2. 用结构体封装共享内存(解决对齐问题)

使用结构体自动处理内存对齐,避免手动计算偏移的错误:

typedef struct {
    volatile bool can_proceed;
    volatile bool acknowledge_read;
    volatile size_t size_read_ptr;
    char buf_1[buf_size];
    char buf_2[buf_size];
    volatile char* buf_1_ptr;
    volatile char* buf_2_ptr;
} SharedData;

// 创建共享内存时使用结构体大小
int shmid=shmget(shm_key, sizeof(SharedData), IPC_CREAT | 0777);
SharedData* shared_mem=(SharedData*)shmat(shmid, NULL, 0);

// 访问成员直接用shared_mem->can_proceed,无需手动计算偏移

3. 修正初始同步逻辑

调整初始状态,确保子进程和父进程的等待条件匹配:

// 初始:子进程可读取数据,父进程等待缓冲区填充
shared_mem->can_proceed = false;
shared_mem->acknowledge_read = true;
shared_mem->size_read_ptr = 1;
shared_mem->buf_1_ptr = shared_mem->buf_1;
shared_mem->buf_2_ptr = shared_mem->buf_2;

同时确保状态切换顺序:

  • 子进程:等待acknowledge_read→读数据→等待can_proceed→交换缓冲区→设置can_proceed=false+acknowledge_read=false
  • 父进程:等待can_proceed==false→读取数据长度→设置acknowledge_read=true→处理缓冲区→设置can_proceed=true

4. 优化输入处理(可选)

原字符逐个解析效率较低,可批量解析整数结合SIMD指令,进一步压缩处理时间,逼近时间限制。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 01:22:07