#include
#include
#include // OpenMP编程需要包含的头文件
int main()
{
#pragma omp parallel for
for(int i = 0; i < 10; ++i)
{
fprintf(stderr, "%d\n", i);
}
return 0;
};
gcc -o a.out main.c -fopenmp
code | comments |
---|---|
#pragma omp single |
开始一个单线程执行域。 |
#pragma omp parallel |
开始一个多线程执行域。 |
#pragma omp for |
在并行域中,将 for() {} 语句撤分为多个线程执行。 |
#pragma omp |
在并行域中,使用单独的线程执行紧接的 for() {} 等。 |
function | comments |
---|---|
int omp_get_num_procs(void) |
返回当前可用的处理器个数。 |
int omp_get_num_threads(void) |
返回当前并行区域中的活动线程个数,如果在并行区域外部调用,返回1。 |
int omp_get_thread_num(void) |
返回当前的线程号。 |
int omp_set_num_threads(void) |
设置进入并行区域时,将要创建的线程个数。 |
#include
#include
#include // OpenMP编程需要包含的头文件
int main()
{
fprintf(stderr, "procs : %d\n", omp_get_num_procs());
#pragma omp parallel //指明下面大括号内部为并行区域
{
fprintf(stderr, "%d\n", i);
}
return 0;
}
#include
#include // OpenMP编程需要包含的头文件
int main()
{
std::cout << "CPU number: " << omp_get_num_procs() << std::endl;
std::cout << "Parallel area 1: " << std::endl;
#pragma omp parallel //指明下面大括号内部为并行区域
{
std::cout << "Num of threads is " << omp_get_num_threads();
std::cout << "; This thread ID is " << omp_get_thread_num() << std::endl;
}
std::cout << "Parallel area 2: " << std::endl;
omp_set_num_threads(4); // 设置成为并行区域创建4个线程
#pragma omp parallel //指明下面大括号内部为并行区域
{
std::cout << "Num of threads is " << omp_get_num_threads();
std::cout << "; This thread ID is " << omp_get_thread_num() << std::endl;
}
return 0;
}
除了并行区域中定义的变量、循环条件变量、private、firstprivate、lastprivate或reduction字句修饰的变量,并行区域中的所有变量都是共享的。例如:
#include
#include // OpenMP编程需要包含的头文件
int main()
{
int share_a = 0; // 共享变量
int share_to_private_b = 1; //通过private子句修饰该变量之后在并行区域内变为私有变量
#pragma omp parallel
{
int private_c = 2;
#pragma omp for private(share_to_private_b)
for (int i = 0; i < 10; ++i) //该循环变量是私有的,若为两个线程,则一个线程执行0 <= i < 5,另一个线程执行5 <= i < 10
{
std::cout << i << std::endl;
}
}
return 0;
}
keyword | comments |
---|---|
private(val1, val2, ...) |
并行区域中变量val是私有的,即每个线程拥有该变量的一个拷贝,但都各自被初始化为0。 |
firstprivate(val1, val2, ...) |
与 private 不同的是,每个线程在开始循环时将会拷贝变量的初始化值。 |
lastprivate(val1, val2, ...) |
与 private 不同的是,每个线程在退出循环时将私有变量将会拷回到原变量中,一般配合 firstprivate 使用。 |
shared(val1, val2, ...) |
声明val是共享的。 |
#include
#include // OpenMP编程需要包含的头文件
int main()
{
int shared_to_private = 1;
#pragma omp parallel for private(shared_to_private)
for (int i = 0; i < 10; ++i)
{
std::cout << shared_to_private << std::endl;
}
return 0;
}
reduction
说明对变量的操作原则,多个线程的执行结果通过reduction中声明的操作符进行计算。以加法为例 reduction (+: sum)
,假设sum的初始值为10,reduction(+: sum)
声明的并行区域中每个线程的sum初始值为0(规定),并行处理结束之后,会将sum的初始化值10以及每个线程所计算的sum值相加。
reduction (operator: var1, val2, ...)
operator | data type | initial |
---|---|---|
+ |
整数、浮点 | 0 |
- |
整数、浮点 | 0 |
* |
整数、浮点 | 1 |
& |
整数 | 所有位均为 1 |
| |
整数 | 0 |
^ |
整数 | 0 |
&& |
整数 | 1 |
|| |
整数 | 0 |
#include
#include // OpenMP编程需要包含的头文件
int main()
{
int sum = 0;
std::cout << "Before: " << sum << std::endl;
#pragma omp parallel for reduction(+: sum)
for (int i = 0; i < 10; ++i)
{
sum = sum + i;
std::cout << sum << std::endl;
}
std::cout << "After: " << sum << std::endl;
return 0;
}
#pragma opm atomic
适用于对变量的复合运算和自增减运算。比如:
#pragma opm atomic
x |= (1 << 2)
#include
#include // OpenMP编程需要包含的头文件
int main()
{
int sum = 0;
std::cout << "Before: " << sum << std::endl;
#pragma omp parallel for
for (int i = 0; i < 20000; ++i)
{
#pragma omp atomic
sum++;
}
std::cout << "After: " << sum << std::endl;
return 0;
}
critical与atomic的区别在于,atomic仅适用于上一节规定的两种类型操作,而且atomic所防护的仅为一句代码。critical可以对某个并行程序块进行防护。
#pragma omp critical [(named)] //[]表示命名是可选
{
//并行程序块,同时只能有一个线程能访问该并行程序块
}
比如:
#prgama omp critical (a)
a = b + c;
#include
#include // OpenMP编程需要包含的头文件
int main()
{
int sum = 0;
std::cout << "Before: " << sum << std::endl;
#pragma omp parallel for
for (int i = 0; i < 100; ++i)
{
#pragma omp critical (a)
{
sum = sum + i;
sum = sum + i * 2;
}
}
std::cout << "After: " << sum << std::endl;
return 0;
}
类似 Mutex
funciton | comments |
---|---|
void opm_init_lock(omp_lock *) |
初始化互斥器 |
void opm_destroy_lock(omp_lock *) |
销毁互斥器 |
void opm_set_lock(omp_lock *) |
获得互斥器 |
void opm_unset_lock(omp_lock *) |
释放互斥器 |
void opm_test_lock(omp_lock *) |
试图获得互斥器,如果获得成功返回true,否则返回false |
#include
#include // OpenMP编程需要包含的头文件
static omp_lock_t lock;
int main()
{
omp_init_lock(&lock); // 初始化互斥锁
#pragma omp parallel for
for (int i = 0; i < 5; ++i)
{
omp_set_lock(&lock); //获得互斥器
std::cout << omp_get_thread_num() << "+" << std::endl;
std::cout << omp_get_thread_num() << "-" << std::endl;
omp_unset_lock(&lock); //释放互斥器
}
omp_destroy_lock(&lock); //销毁互斥器
return 0;
}
前面所有的示例都带有隐式栅障,即并行区域中所有线程执行完毕之后,主线程才继续执行。
keyword | function | example |
---|---|---|
nowait |
不等待指定的并行语句执行完成,继续执行下面的语句。 | #pragma omp for nowait |
barrier |
显示同步,等待前面的线程结束。 | #pragma omp barrier |
master |
声明对应的并行程序块只由主线程完成。 | #pragma omp mater |
section |
用来指定不同的线程执行不同的部分。 | #pragma omp parallel sections {} #pragma omp section {} |
#include
#include // OpenMP编程需要包含的头文件
int main()
{
#pragma omp parallel
{
#pragma omp for nowait
for (int i = 0; i < 1000; ++i)
{
std::cout << i << "+" << std::endl;
}
#pragma omp for
for (int j = 0; j < 10; ++j)
{
std::cout << j << "-" << std::endl;
}
}
return 0;
}
#include
#include // OpenMP编程需要包含的头文件
int main()
{
#pragma omp parallel
{
for (int i = 0; i < 100; ++i)
{
std::cout << i << "+" << std::endl;
}
#pragma omp barrier
for (int j = 0; j < 10; ++j)
{
std::cout << j << "-" << std::endl;
}
}
return 0;
}
#include
#include // OpenMP编程需要包含的头文件
int main()
{
#pragma omp parallel
{
#pragma omp master
{
for (int j = 0; j < 10; ++j)
{
std::cout << j << "-" << std::endl;
}
}
std::cout << "This will printed twice." << std::endl;
}
return 0;
}
#include
#include // OpenMP编程需要包含的头文件
int main()
{
#pragma omp parallel sections //声明该并行区域分为若干个section,section之间的运行顺序为并行的关系
{
#pragma omp section //第一个section,由某个线程单独完成
for (int i = 0; i < 5; ++i)
{
std::cout << i << "+" << std::endl;
}
#pragma omp section //第一个section,由某个线程单独完成
for (int j = 0; j < 5; ++j)
{
std::cout << j << "-" << std::endl;
}
}
return 0;
}
默认情况下会自动生成与CPU个数相等的线程,然后并行执行并行区域中的代码。通过对并行区域中的for循环进行特殊的声明,可以将for循环的不同部分分配给不同的线程运行。然后再通过锁同步或事件同步来实现并行区域的同步控制。
policy | comments | condition |
---|---|---|
static |
循环变量区域分为n等份,每个线程评分n份任务 | 各个cpu的性能差别不大 |
dynamic |
循环变量区域分为n等份,某个线程执行完1份之后执行其他需要执行的那一份任务 | cpu之间运行能力差异较大 |
guided |
循环变量区域由大到小分为不等的n份,运行方法类似于 dynamic |
由于任务份数比 dynamic ,所以可以减少调度开销 |
runtime |
在运行时来适用上述三种调度策略中的一种,默认是使用 static |
使用 ... for schedule(
将并行任务按 policy
调度策略,for
循环每 n
迭代分成一个任务。
#include
#include // OpenMP编程需要包含的头文件
int main()
{
#pragma omp parallel for schedule(static, 2) //static调度策略,for循环每两次迭代分成一个任务
for (int i = 0; i < 10; ++i) //被分成了5个任务,其中循环0~1,4~5,8~9分配给了第一个线程,其余的分配给了第二个线程
{
std::cout << "Thread ID:" << omp_get_thread_num() << " Value:" << i << std::endl;
}
return 0;
}
#include
#include // OpenMP编程需要包含的头文件
int main()
{
#pragma omp parallel for schedule(dynamic, 2) //dynamic调度策略,for循环每两次迭代分成一个任务
for (int i = 0; i < 10; ++i) //被分成了5个任务,只要有任务并且线程空闲,那么该线程会执行该任务
{
std::cout << "Thread ID:" << omp_get_thread_num() << " Value:" << i << std::endl;
}
return 0;
}