openmp之互斥锁

    openmp并行程序设计时,经常会发生线程之间的竞争,即可能同时访问同一共享变量。当然如果共享变量在线程并发期间不改变值的话,同时读取共享变量不会产生任何问题,除非同时写该变量,则无法确定该线程间该变量的写顺序。因此需要设置隔离区,线程之间只能顺序去执行该隔离区的命令。
    openmp中有一些同步机制可以避免线程竞争问题的发生。可以使用同步机制,使得线程只有执行到某个语句,才能继续执行后面的程序。在程序需要访问可能产生竞争的内存数据的时候,都需要插入相应的临界区代码。
    一个例子是定积分确定求圆周率Pi的值:
        
    函数接口只需要确定区间0-1之间的分段数,段数越多,PI的值自然越精确。
   串行程序非常简单:
double get_pi(const int& N)
{
    assert(N > 0);
    double pi = 0;
    int i;
    double x;
    double sigma = 1.0/N;
    for (i = 0; i < N; ++i)
    {
        x = sigma*(i + 0.5);
        pi += sigma*4.0/(1.0 + x*x);
    }
    return pi;
}


    而使用openmp的并行程序中,求取不同段的段值之间并没有数据相关性,因此存在潜在的并行性。唯一的问题是如何定义每段的段值变量,是用一个长度为段数的数组,还是只用一个变量定义即可呢?前者的并行思路非常清晰,但是如果段数很多,无疑需要很大的空间;后者当然是我们所追求的,既能加速又能避免不必要的空间开销。但如果不同线程计算段值使用同一变量,如何避免线程之间的竞争呢?
    很明显,只要隔离开不同线程之间的操作顺序即可。通过临界区,使得在整体求和过程中只有一个线程能够同时执行求和部分代码;或者使用互斥锁函数或原子操作等,不同的线程执行各自的段值私有变量的求和,线程之间也是隔离开来。
    在OpenMP中,提供了三种不同的互斥锁机制用来对一块内存进行保护,它们分别是:
           (1) 临界区(critical)
           (2) 原子操作(atomic)
           (3) 库函数来提供同步操作
double get_pi_omp(const int& N)
{
    assert(N > 0);
    double pi = 0;
    double sigma = 1.0/N;
    int i;
    double x;
    double sum = 0;
//#pragma omp parallel private(x) firstprivate(sum) num_threads(4)
//    {
//        #pragma omp for 
//        for (i = 0; i < N; ++i)
//        {
//            x = sigma*(i + 0.5);
//            sum += sigma*4/(x*x + 1);
//            //printf("threadid = %d, sum = %f\n", omp_get_thread_num(), sum);
//        }
//        #pragma omp critical
//        {
//            pi += sum;
//        }
//    }
    omp_lock_t mylock;
    omp_init_lock(&mylock);
#pragma omp parallel private(x) firstprivate(sum) num_threads(4)
    {
        #pragma omp for 
        for (i = 0; i < N; ++i)
        {
            x = sigma*(i + 0.5);
            sum += sigma*4/(x*x + 1);
            //printf("threadID = %d, sum = %f\n", omp_get_thread_num(), sum);
        }
        omp_set_lock(&mylock);
        pi += sum;
        omp_unset_lock(&mylock);
    }
    omp_destroy_lock(&mylock);

    //#pragma omp parallel private(x) firstprivate(sum) num_threads(4)
    //    {
    //        #pragma omp for 
    //        for (i = 0; i < N; ++i)
    //        {
    //            x = sigma*(i + 0.5);
    //            sum += sigma*4/(x*x + 1);
    //            //printf("threadID = %d, sum = %f\n", omp_get_thread_num(), sum);
    //        }
    //        #pragma omp atomic
    //        pi += sum;
    //    }
    return pi;
}

    在main函数中检测,设置0-1之间的分段数n = 120000000, 检测代码以及结果如图:
int main(int argc, char** argv)
{
    int n;
    puts("Input n\n");
    scanf("%d", &n);
    //get pi value and calcute cost sequential time
    clock_t first = clock();
    double pi = get_pi(n);
    clock_t last = clock();
    printf("pi = %f \n", pi);
    printf("The sequential cost time is %f seconds\n", (double)(last - first)/CLOCKS_PER_SEC);
    //get pi value and calcute cost parallel time
    first = clock();
    pi = get_pi_omp(n);
    last = clock();
    printf("--------------------------------------------------\n");
    printf("pi = %f\n", pi);
    printf("The parallel cost time is %f seconds\n", (double)(last - first)/CLOCKS_PER_SEC);
    getchar();
    return 0;
}
        

    在 双核四线程i3 CPU处理器下,开启四个线程,三种互斥锁的方式效率是极其相近的, 都在0.53s左右,而串行执行为1.08s。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值