OpenMP使用
1、并行的原则
1. 线程安全
- 避免数据竞争:确保多个线程不会同时访问和修改共享数据,或者确保对共享数据的访问是线程安全的。这通常涉及使用锁、原子操作或者线程私有数据。
- 可重入性:函数应当是可重入的,即函数在并行执行时,每个线程的执行不应影响其他线程的执行。
2. 独立性
- 独立计算:函数中的计算应尽可能独立,避免线程间的相互依赖。每个线程处理的任务应当是独立的,以减少同步开销。
- 无副作用:函数应当避免对全局状态进行修改,尤其是在并行执行时,这可能会导致难以追踪的错误。
3. 分解任务
- 粒度控制:任务的粒度应当适当。任务过于细小可能导致过多的线程切换和同步开销,而任务过于粗大则可能导致负载不均衡。
- 数据分割:将数据分割成适合并行处理的小块,使每个线程能够独立处理一部分数据。
4. 同步和互斥
- 锁和原子操作:当访问共享资源时使用锁或原子操作来避免数据竞争。尽量减少锁的使用频率和粒度,以减少性能开销。
- 同步点:在必要时使用同步机制(如屏障)来确保所有线程完成特定任务后再继续执行。
5. 避免隐式共享
- 私有数据:确保每个线程有自己的私有数据副本,避免隐式的共享数据。例如,循环变量和局部变量应在每个线程中独立存在。
- 减少共享数据:尽量减少共享数据的使用,优先使用线程本地存储和局部变量。
6. 任务调度
- 负载均衡:任务的划分应考虑负载均衡,确保每个线程处理的任务量大致相同。任务划分不均可能导致某些线程空闲,而其他线程则超负荷运行。
- 动态调度:在某些情况下,可以使用动态调度策略来处理负载不均的问题,特别是当任务的工作量不均匀时。
7. 错误处理
- 检测和处理错误:并行代码中的错误可能比串行代码中的错误更难发现和调试。确保在并行函数中实现适当的错误处理和异常捕获机制。
2、OpenMP
对部分代码使用并行的方式
- 使用前需要先在vs中打开对
openMP
的支持,否则不支持多线程 - 在需要并行的部分添加
pragma omp parallel
,大括号要在下一行,不能放在同行 - 要指定线程数,没有指定默认在所有核上执行(取决于计算机的性能)
void parallel_for() {
int n = 9;
int i = 0;
omp_set_num_threads(4);
#pragma omp parallel shared(n) private(i)
{
#pragma omp for
for(i = 0; i < n; i++) {
printf("Thread %d executes loop iteration %d\n", omp_get_thread_num(),i);
}
}
}
int main(int argc, char** argv){
parallel_for();
return 0;
}
结果输出:
- 结果中每一个线程的执行顺序由系统调度,执行的顺序并不相同,每个线程执行的次数也不同
- shared指定了线程之间数据的共享;private则指定每个线程独有的变量
>>>
Thread 0 executes loop iteration 0
Thread 0 executes loop iteration 1
Thread 0 executes loop iteration 2
Thread 2 executes loop iteration 5
Thread 2 executes loop iteration 6
Thread 3 executes loop iteration 7
Thread 3 executes loop iteration 8
Thread 1 executes loop iteration 3
Thread 1 executes loop iteration 4
3、线程间通信
- 多线程执行时,线程间的数据通信是保证代码正确执行的关键,对于线程间的共享数据
shared
指定,线程的私有变量private
指定 - 在并行域使用共享变量时,如果存在写操作,需要对变量保存,保证写入的正确性。可能存在多个线程同时修改共享变量或者一个线程读取共享变量一个线程更新共享变量的情况,这种操作会引起程序错误
private
子句用来指定哪些数据是线程私有的, 即每个线程具有变量的私有副本, 线程之间互不影响. 其语法形式为private(list)
4、关键字的使用
-
private
指定那些数据是线程私有的,每个线程具有变量的私有副本,线程之间互不影响 -
lastprivate
退出并行域时,其修饰变量的最后取值会保存下来 -
firstprivate
用于为private修饰的变量提供初始值。使用firstprivate
修饰的变量会使用在前面定义的同名变量的值作为其初始值。如果是数组,标识将前面数组的值传递给后面的操作中void test_firstprivate() { int n = 8; int i = 0, a[8]; for (int i = 0; i < n; i++) { a[i] = i+1; } omp_set_num_threads(OMP_NUM_THREADS); #pragma omp parallel for private(i) firstprivate(a) for (i = 0; i < n; i++) { printf("thread id(%d):a[%d] is:%d\n", omp_get_thread_num(),i, a[i]); } }
-
shared
指定那些数据在线程之间共享。共享变量需要维护,保证不同线程对数据的操作结果正确 -
nowait
取消并行结构中的隐式屏障void test_nowait() { int i, n = 6; omp_set_num_threads(OMP_NUM_THREADS); #pragma omp parallel { #pragma omp for nowait for (i = 0; i < n; i++) { printf("thread id (%d):++++\n", omp_get_thread_num()); } #pragma omp for for (i = 0; i < n; i++) { printf("thread id (%d):----\n", omp_get_thread_num()); } } }
没有
nowait
修饰时执行完第一个循环才开始执行第二个循环,添加nowait
之后不会等到第一个循环结束,而是会交替执行 -
schedule
只作用于循环结构没设置循环任务的调度方式schedule(kind, chunk_size)
-
default
子句用于设置变量默认的data-sharing属性, 在C/C++中只支持default(none | shared)
, 其中default(shared)
设置所有的变量默认为共享的,default(none)
取消变量的默认属性, 需要显示指定变量是共享的还是私有的.
5、线程同步
同步主要是针对多个线程之间对共享变量的访问。保证线程以一定的顺序更新共享变量,或者保证两个或多个线程不同时修改共享变量
-
barrier
同步路障(
barrier
), 当线程遇到路障时必须要停下等待, 直到并行区域中的所有线程都到达路障点, 线程才继续往下执行. 在每一个并行域和任务分担域的结束处都会有一个隐式的同步路障, 即在parallel、for、sections、single
构造的区域之后会有一个隐式的路障, 因此在很多时候我们无需显示的插入路障. -
ordered
ordered结构允许在并行域中以串行的顺序执行一段代码, 如果我们在并行域中想按照顺序打印被不同的线程计算的数据, 就可以使用这个子句, 下面是语法形式
#pragma omp ordered structured block
-
ordered 只作用于循环结构(
loop construct
) -
使用ordered时需要在构造并行域的时候加上
ordered
子句, 如下面所示#pragma omp parallel for ordered
3、critical
标识为临界区,保证在任意一个时间内只有一个线程执行该区域中的代码。一个线程要进入临界区,必须等待临界区处于空闲状态
4、
reduction
并行中对多个线程计算的结果进行规约。语法:
reduction(operation:list)
支持的操作
操作 原始值 + 0 - 1 * 0 & ~0 | 0 ^ 0 & 1 || 0 -
Code
#include <stdio.h>
#include <omp.h>
#include <time.h>
#include <Windows.h>
#include <sys\timeb.h>
#define OMP_NUM_THREADS 4
#pragma warning( disable : 4996 )
void parallel_for() {
int n = 9;
int i = 0;
omp_set_num_threads(4);
#pragma omp parallel shared(n) private(i)
{
#pragma omp for
for(i = 0; i < n; i++) {
printf("Thread %d executes loop iteration %d\n", omp_get_thread_num(),i);
}
}
}
void funA()
{
printf("In funA : this section is executed by thread:%d\n",
omp_get_thread_num());
}
void funB()
{
printf("In funB: this section is executed by thread:%d\n",
omp_get_thread_num());
}
void parallel_section()
{
#pragma omp parallel
{
#pragma omp sections
{
#pragma omp section
{
(void)funA();
}
#pragma omp section
{
(void)funB();
}
}
}
}
void test_private()
{
int n = 16;
int i = 2, a = 3;
omp_set_num_threads(OMP_NUM_THREADS);
#pragma omp parallel for private(i, a)
for (i = 0; i < n; i++) {
a = i + 1;
printf("In for: thread (%d) has a value of a = %d , for i = %d\n", omp_get_thread_num(),a,i);
}
printf("\n");
printf("Out for: thread %d has a value of a = %d for i = %d\n", omp_get_thread_num(),a,i);
}
void test_nowait()
{
int i, n = 6;
omp_set_num_threads(OMP_NUM_THREADS);
#pragma omp parallel
{
#pragma omp for nowait
for (i = 0; i < n; i++) {
printf("thread id (%d):++++\n", omp_get_thread_num());
}
#pragma omp for
for (i = 0; i < n; i++) {
printf("thread id (%d):----\n", omp_get_thread_num());
}
}
}
void test_firstprivate()
{
int n = 8;
int i = 0, a[8];
for (int i = 0; i < n; i++) {
a[i] = i+1;
}
omp_set_num_threads(OMP_NUM_THREADS);
#pragma omp parallel for private(i) firstprivate(a)
for (i = 0; i < n; i++) {
printf("thread id(%d):a[%d] is:%d\n", omp_get_thread_num(),i, a[i]);
}
}
void test_schedule()
{
int i, n = 10;
#pragma omp parallel for default(none) schedule(static, 2) \
private(i) shared(n)
for (i = 0; i < n; i++) {
printf("Iteration %d executed by thread %d\n", i, omp_get_num_threads());
}
}
#define NOW_TIME(buf, len) { \
time_t nowtime; \
nowtime = time(NULL); \
struct tm *local; \
local = localtime(&nowtime); \
strftime(buf, len, "%H:%M:%S", local); \
}
void print_time(int tid, char* s ) {
int len = 10;
char buf[10];
NOW_TIME(buf, len);
printf("Thread %d %s at %s\n", tid, s, buf);
}
void test_barrier() {
int tid;
omp_set_num_threads(OMP_NUM_THREADS);
#pragma omp parallel private(tid)
{
tid = omp_get_thread_num();
if (tid < omp_get_num_threads() / 2) {
Sleep(3000);
}
print_time(tid, "before barrier ");
//#pragma omp barrier
print_time(tid, "after barrier ");
}
}
/*
加上barrier
Thread 3 before barrier at 16:54:20
Thread 2 before barrier at 16:54:20
Thread 0 before barrier at 16:54:23
Thread 1 before barrier at 16:54:23
Thread 3 after barrier at 16:54:24
Thread 1 after barrier at 16:54:24
Thread 0 after barrier at 16:54:24
Thread 2 after barrier at 16:54:24
不加barrier
Thread 3 before barrier at 16:56:33
Thread 2 before barrier at 16:56:33
Thread 2 after barrier at 16:56:33
Thread 3 after barrier at 16:56:33
Thread 1 before barrier at 16:56:36
Thread 1 after barrier at 16:56:36
Thread 0 before barrier at 16:56:36
Thread 0 after barrier at 16:56:36
*/
void test_ordered()
{
int i, tid, n = 5;
int a[5];
for (i = 0; i < n; i++) {
a[i] = 0;
}
omp_set_num_threads(OMP_NUM_THREADS);
#pragma omp parallel for default(none) ordered \
private(i, tid) shared(n, a)
for (i = 0; i < n; i++) {
tid = omp_get_thread_num();
printf("Thread %d updates a[%d]\n", tid, i);
a[i] += i;
#pragma omp ordered
{
printf("Thread %d printf value of a[%d] = %d\n", tid, i, a[i]);
}
}
}
void test_critical()
{
int n = 1000, sum = 0, sumLocal, i, tid;
int a[1000];
for (i = 0; i < n; i++) {
a[i] = i;
}
omp_set_num_threads(OMP_NUM_THREADS);
#pragma omp parallel shared(n, a, sum) private(tid, sumLocal)
{
tid = omp_get_thread_num();
sumLocal = 0;
#pragma omp for
for (i = 0; i < n; i++) {
sumLocal += a[i];
}
#pragma omp critical(update_sum)
{
sum += sumLocal;
printf("Thread %d:sumLocal = %d, sum = %d\n", tid, sumLocal, sum);
}
}
}
void test_reduction()
{
int i, sum = 0;
int n = 1000;
omp_set_num_threads(OMP_NUM_THREADS);
#pragma omp parallel for reduction(+:sum)
for (i = 0; i < n; i++) {
sum += i;
}
printf("sum = %d", sum);
}
int main(int argc, char** argv){
test_reduction();
return 0;
}
推荐学习网站:参考网站