线程同步之互斥锁函数
前文介绍了互斥锁同步的两种方法: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. 互斥锁示例
view plain
copy to clipboard
print
?
·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
- #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;
- }
#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
示例:
view plain
copy to clipboard
print
?
·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
- #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;
- }
#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
view plain
copy to clipboard
print
?
·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
- #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;
- }
#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来声明对应的并行程序块只有主线程完成。
view plain
copy to clipboard
print
?
·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
- #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;
- }
#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用来指定不同的线程执行不同的部分
下面通过一个实例来说明其使用方法:
view plain
copy to clipboard
print
?
·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
- #include <iostream>
- #include <omp.h>
-
- int main()
- {
-
-
-
- #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;
- }
#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同时执行。