FFTW3 DLL多线程调用报错求助:访问违规读取内存
将封装了FFTW3傅里叶函数的DLL通过OpenMP多线程调用时出现异常:多次调用DLL中的dst(基于DFT实现的离散正弦变换)和IDST函数时,触发access violation reading location错误,错误发生在dst函数的// write x to y代码段。
相关代码
DLL中的dst函数实现
void dst(fftwf_complex* x, size_t n) { fftwf_complex* y = fftwf_alloc_complex(size_t); fftwf_plan plan = fftwf_plan_dft_1d(size_t,y,y,FFTW_FORWARD,FFTW_ESTIMATE); // write x to y fftwf_execute(plan); fftwf_destroy_plan(plan); fftwf_cleanup(); // write y to x again fftwf_free(y); }
外部OpenMP多线程调用代码
double Class::Function1(Class& Inputs, int x, int y, int z) { dllFunction(Inputs.a, Inputs.b, Inputs.c, x, y, z); return Inputs.z; } void Class::Function2(Class& Inputs) { #pragma omp parallel { #pragma omp for for (int i=0;i<5;i++) { Inputs.z = Obj.Function1(Inputs,x,y,z); } } }
注:因FFTW3原生DST函数处理大数据时速度较慢,故采用DFT实现DST。
问题分析与解决方案
1. 内存分配与Plan创建的致命语法错误
dst函数存在低级参数错误:
fftwf_alloc_complex(size_t)和fftwf_plan_dft_1d(size_t,y,y,...)中,错误地将类型名size_t作为参数传入,实际应该使用函数参数n。这会导致分配的内存大小完全不符合需求(传入的是类型占位符的固定值,远小于实际需要的内存),后续写入y时必然触发内存越界,多线程下这种越界冲突会直接引发access violation。
2. fftwf_cleanup()的滥用
fftwf_cleanup()是FFTW的全局清理函数,负责释放FFTW内部的全局资源(如内存池、规划缓存)。绝对不能在每个函数调用中执行该操作:多线程环境下,当一个线程调用fftwf_cleanup()时,其他线程可能正在使用这些全局资源,直接导致资源失效,引发内存访问错误。正确做法是在整个程序退出前调用一次即可。
3. OpenMP线程的共享变量竞争
外部调用代码中,Inputs.z是多线程共享变量,在#pragma omp for循环中直接赋值会导致线程竞争。虽然这不一定是当前access violation的直接原因,但会导致结果不可预期,建议使用线程私有变量存储临时结果,再通过同步机制(如#pragma omp atomic)更新共享变量。
4. FFTW多线程调用的注意事项
FFTW的plan创建和执行本身是线程安全的,但要确保每个线程使用独立的plan和内存缓冲区(你当前每个函数调用创建独立plan和y缓冲区的思路是正确的,只需修正前面的参数错误)。如果需要启用FFTW自身的多线程支持,需在程序初始化时调用fftwf_init_threads(),并在创建plan时指定线程数,但你这里是用OpenMP多线程调用单线程FFTW函数,无需额外设置FFTW的线程选项。
修正后的dst函数示例
void dst(fftwf_complex* x, size_t n) { // 用参数n分配正确大小的内存 fftwf_complex* y = fftwf_alloc_complex(n); // 用n作为DFT的长度参数 fftwf_plan plan = fftwf_plan_dft_1d(n, y, y, FFTW_FORWARD, FFTW_ESTIMATE); // 正确实现x到y的写入,确保长度匹配n for (size_t i = 0; i < n; ++i) { y[i][0] = x[i][0]; y[i][1] = x[i][1]; // 补充DST实现所需的输入预处理逻辑(如镜像扩展) } fftwf_execute(plan); fftwf_destroy_plan(plan); // 移除fftwf_cleanup(),移至程序退出时调用 // 正确实现y到x的写入,确保长度匹配n for (size_t i = 0; i < n; ++i) { x[i][0] = y[i][0]; x[i][1] = y[i][1]; // 补充DST实现所需的输出后处理逻辑 } fftwf_free(y); }
外部调用的修正建议
void Class::Function2(Class& Inputs) { #pragma omp parallel { #pragma omp for for (int i=0;i<5;i++) { // 用线程私有变量存储临时结果 double temp_z = Obj.Function1(Inputs, x, y, z); // 原子操作更新共享变量,避免竞争 #pragma omp atomic Inputs.z = temp_z; // 可根据业务逻辑调整同步方式(如累加、覆盖) } } }
内容的提问来源于stack exchange,提问作者Hansov

