线程同步之互斥锁函数
前文介绍了互斥锁同步的两种方法:atomic和critical,本章介绍OpenMP提供的互斥锁函数。互斥锁函数类似于Windows、Linux下的mutex。
1. 互斥锁函数
函数声明 功能
void omp_init_lock(omp_lock*) 初始化互斥器
void omp_destroy_lock(omp_lock*) 销毁互斥器
void omp_set_lock(omp_lock*) 获得互斥器
void omp_unset_lock(omp_lock*) 释放互斥器
void omp_test_lock(omp_lock*) 试图获得互斥器,如果获得成功则返回true,否则返回false
2. 互斥锁示例
- #include <iostream>
- #include <omp.h>
- 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;
- }
上边的示例对for循环中的所有内容进行加锁保护,同时只能有一个线程执行for循环中的内容。
线程1或线程2在执行for循环内部代码时不会被打断。如果删除代码中的获得锁释放锁的代码,则相当于没有互斥锁。
互斥锁函数中只有omp_test_lock函数是带有返回值的,该函数可以看作是omp_set_lock的非阻塞版本。
线程同步之事件同步机制
1. 引言
前边已经提到,线程的同步机制包括互斥锁同步和事件同步。互斥锁同步包括atomic、critical、mutex函数,其机制与普通多线程同步的机制类似。而事件同步则通过nowait、sections、single、master等预处理指示符声明来完成。
2. 隐式栅障
在开始之前,先介绍一下并行区域中的隐式栅障。
栅障(Barrier)是OpenMP用于线程同步的一种方法。线程遇到栅障时必须等待,直到并行的所有线程都到达同一点。
注意:
在任务分配for循环和任务分配section结构中隐含了栅障,在parallel, for, sections, single结构的最后,也会有一个隐式的栅障。
隐式的栅障。
隐式的栅障会使线程等到所有的线程继续完成当前的循环、结构化块或并行区,再继续执行后续工作。可以使用nowait去掉这个隐式的栅障。
3. nowait事件同步
nowait用来取消栅障,其用法如下:
#pragma omp for nowait //不能使用#pragma omp parallel for nowait
或
#pragma omp single nowait
示例:
- #include <iostream>
- #include <omp.h>
- 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;
- }
运行程序,可以看到第一个for循环的两个线程中的一个执行完之后,继续向下执行,因此同时打印了第一个循环的+和第二个循环的-。
如果去掉第一个for循环的nowait生命,则第一个for循环的两个线程都执行完之后,才开始同时执行第二个for循环。也就是说,通过#pragma omp for声明的for循环结束时有一个默认的隐式栅障。
4. 显示同步栅障 #pragma omp barrier
- #include <iostream>
- #include <omp.h>
- int main()
- {
- #pragma omp parallel
- {
- for(int i = 0; i < 100; ++i)
- {
- std::cout << i << "+" << std::endl;
- }
- #pragma om barrier
- for(int j = 0; j < 10; ++j)
- {
- std::cout << j << "-" << std::endl;
- }
- }
- return 0;
- }
运行程序,可以看出两个线程执行了第一个for循环,当两个线程同时执行完第一个for循环之后,在barrier处进行了同步,然后执行后边的for循环。
5. master事件同步
通过#pragma om master来声明对应的并行程序块只有主线程完成。
- #include <iostream>
- #include <omp.h>
- 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;
- }
运行程序,可以看到,进入parallel声明的并行区域之后,创建了两个线程。主线程执行了for循环,而另一个线程没有执行for循环,而直接进入了for循环之后的打印语句,然后执行for循环的线程随后还会再执行一次后边的打印语句。
6. sections用来指定不同的线程执行不同的部分
下面通过一个实例来说明其使用方法:
- #include <iostream>
- #include <omp.h>
- int main()
- {
- //声明该并行区域分为若干个section,section之间的运行顺序为并行
- //的关系
- #pragma omp parallel sections
- 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;
- }
可以看到,并行区域中有两个线程,所以两个section同时执行。