OpenMP - 编译制导(四)- critical、atomic、flush
critical制导
critical
用于在多线程环境中确保临界区代码的互斥执行。临界区是一段代码,当多个线程同时尝试访问共享资源时,只允许一个线程执行,以防止数据竞争和不确定行为。由于 critical
区域会引入串行化,过度使用会降低并行性能,因此应谨慎使用,只在必要时才使用 critical
指令。保护对共享变量的修改
#pragma omp critical [(name)]
name是可选项,用于标识临界区的名称,以便更细粒度地控制并发访问。
- 有name:不同name的临界区对应不同的共享资源或者不同的逻辑部分,允许多个线程同时进入,以便并行执行。
- 无name:不使用名称则会将所有临界区视为一个全局的临界区,只有一个线程可以在任何给定时间执行这些临界区中的代码。
注:不同名称的临界区允许并行执行,但同一临界区的互斥访问规则仍然适用,无论临界区内操作的变量是否相同。
#include <iostream>
#include <omp.h>
using namespace std;
int main(int argc, char* argv[]){
#pragma omp parallel
{
int id = omp_get_thread_num();
#pragma omp critical
{
cout << "thread " << id << " excute name1" << endl;
}
cout << "after1 " << endl;
#pragma omp critical
{
cout << "thread " << id << " excute name2" << endl;
}
cout << "after2 " << endl;
}
return 0;
}
non name 同一个临界区
thread 0 excute name1
thread 1 excute name1
after1
thread 3 excute name1
after1
after1
thread 2 excute name1
after1 thread
1 excute name2
after2
thread 3 excute name2
after2
thread 0 excute name2
after2
thread 2 excute name2
after2
name a & name b
thread 0 excute name1
thread 3 excute name1
after1
thread 3 excute name2
after2
thread 1 excute name1
after1
thread 1 excute name2
after2
after1
thread 0 excute name2
after2
thread 2 excute name1
after1
thread 2 excute name2
after2
atomic制导
atomic
用于确保对共享变量的原子操作,以避免多个线程同时尝试访问和修改共享变量时可能发生的不确定行为。
注:虽然atomic
和critical
都用于同步,但它们的应用场景有所不同。critical
指令可以应用于任意只需要一个线程执行的地方,而atomic
指令则专用于内存读写操作。此外,使用critical
指令可能会对性能产生较大的影响,因为它会阻塞其他线程直到当前线程退出临界区。
- 无论何时,当需要在更新共享存储单元的语句中避免数据竞争,应该先使用atomic,然后再使用临界段。(atomic编译指导允许并行的更新数组内的不同元素;而使用临界制导时数组元素的更新是串行的)
- 只会作用于紧随其后的单条语句
#pragma omp atomic
statement
C/C++中,statement必须是下列形式之一:x binop=expr、x++ 、x-- 、++x 、--xx 。其中:binop是二元操作符:+、- 、* 、/ 、& 、^ 、<< 、>>。
#include <iostream>
#include <omp.h>
using namespace std;
int main(int argc, char* argv[]){
int a[10], b[10] = {1,1,1,2,3,4,2,5,0,9};
#pragma omp parallel for
for (int i = 0; i < 10; ++i) {
a[i] = 0;
}
#pragma omp parallel for
for (int i = 0; i < 10; ++i) {
#pragma omp atomic
a[b[i]] += i;
}
for (int i = 0; i < 10; ++i) {
cout << a[i] << " ";
}
return 0;
}
atomic
8 3 9 4 5 7 0 0 0 9
flush制导
flush
用于确保线程间的数据一致性。在多线程并行执行时,由于每个线程都有自己的本地缓存,可能会导致数据的不一致性,即一个线程所做的修改可能无法立即被其他线程看到。flush
可以用来强制将本地缓存中的数据刷新到主内存中,以确保所有线程对共享数据的修改对其他线程可见。
#pragma omp flush (list)
list是一个由共享变量组成的列表,表示需要刷新到主内存的共享变量。如果没有提供list参数,则默认刷新所有共享变量的值。
#include <iostream>
#include <omp.h>
using namespace std;
int main(int argc, char* argv[]){
int k = 0, d[10];
#pragma omp parallel num_threads(10)
{
int id = omp_get_thread_num();
if (id == 5){
k = 1;
#pragma omp flush (k)
}else{
#pragma omp flush (k)
d[id] = k;
}
}
for (int i = 0; i < 10; ++i) {
cout << "thread " << i << " -> k = " << d[i] << endl;
}
return 0;
}
flush
thread 0 -> k = 0
thread 1 -> k = 0
thread 2 -> k = 0
thread 3 -> k = 1
thread 4 -> k = 1
thread 5 -> k = 0
thread 6 -> k = 0
thread 7 -> k = 0
thread 8 -> k = 0
thread 9 -> k = 1
。。。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧