启用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
相关产品推荐
相关产品推荐

