OpenMP命名临界区:变量参数会被求值还是当作字符串使用?
我有如下代码,希望为其添加omp for指令:
for ( int i = 0 ; i < N ; i++ ) { double max2 = 0.; (calculate max2); for ( int j = 0 ; j < 3 ; j++) { int m = group[i].member[j]; if ( members[m].norm < max2 ) { members[m].norm = max2; } } }由于需要保护对members的访问,又想避免循环序列化带来的性能损耗,我计划使用命名临界区,修改后的代码如下:
#pragma omp parallel for for ( int i = 0 ; i < N ; i++ ) { double max2 = 0.; (calculate max2); for ( int j = 0 ; j < 3 ; j++) { int m = group[i].member[j]; #pragma omp critical (m) if ( members[m].norm < max2 ) { members[m].norm = max2; } } }我的疑问是:此处的"m"是被当作临界区名称的字符串,还是会被求值并以其数值作为临界区名称?代码可编译,但不确定是否符合预期,望专业人士解释OpenMP在此处的实现逻辑,非常感谢。
核心结论
先给你明确答案:你写的(m)会被OpenMP当作字面量的临界区名称字符串,而不是对变量m求值后的数值。也就是说,所有线程进入的都是名为"m"的同一个临界区,这和你想要的“每个m对应独立临界区”的预期完全不符,本质上还是全局锁,会严重损耗并行性能。
OpenMP命名临界区的规则
根据OpenMP标准,critical指令后的名称必须是一个标识符(identifier)——也就是代码里的静态名称(比如变量名、函数名这类),它会被直接当作字符串字面量处理,不会被动态求值。
举两个直观例子:
#pragma omp critical (my_lock):临界区名称是字符串"my_lock"#pragma omp critical (m):名称就是字符串"m",和你代码里的变量m没有任何关联
实现你需求的正确方式
你的目标是:对同一个members[m]的访问互斥,不同m的访问可以并行。命名临界区做不到这点,因为它的名称是编译期确定的,无法动态变化。推荐两种可行方案:
1. 使用数组形式的互斥锁(最通用)
提前初始化一个和members数组大小一致的锁数组,每个m对应独立的锁:
// 初始化锁数组 omp_lock_t *locks = malloc(sizeof(omp_lock_t) * num_members); for (int k = 0; k < num_members; k++) { omp_init_lock(&locks[k]); } #pragma omp parallel for for (int i = 0 ; i < N ; i++ ) { double max2 = 0.; (calculate max2); for (int j = 0 ; j < 3 ; j++) { int m = group[i].member[j]; omp_set_lock(&locks[m]); // 锁定对应m的锁 if (members[m].norm < max2) { members[m].norm = max2; } omp_unset_lock(&locks[m]); // 解锁 } } // 销毁锁并释放内存 for (int k = 0; k < num_members; k++) { omp_destroy_lock(&locks[k]); } free(locks);
这种方式能真正实现“同m串行、异m并行”,最大化并行效率。
2. 使用原子操作(轻量替代,需编译器支持OpenMP 4.5+)
你的逻辑是“当members[m].norm小于max2时更新”,可以用OpenMP的原子比较操作实现,比锁更轻量:
#pragma omp parallel for for (int i = 0 ; i < N ; i++ ) { double max2 = 0.; (calculate max2); for (int j = 0 ; j < 3 ; j++) { int m = group[i].member[j]; // OpenMP 4.5+支持的原子比较操作 #pragma omp atomic compare if (members[m].norm < max2) { members[m].norm = max2; } } }
如果编译器不支持该特性,也可以用基于CAS(比较并交换)的手动循环实现类似逻辑。
为什么你的代码能编译?
编译器把m当作合法的临界区名称标识符,所以不会报错,但行为完全不符合预期——所有线程争抢同一个"m"临界区,和直接用无名称的#pragma omp critical效果一致,根本没实现按m区分的互斥。
内容的提问来源于stack exchange,提问作者pattakosn

