最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解OpenMP的線程同步機(jī)制

 更新時(shí)間:2023年01月21日 09:28:35   作者:一無是處的研究僧  
在本篇文章當(dāng)中主要給大家介紹?OpenMP?當(dāng)中線程的同步和互斥機(jī)制,在?OpenMP?當(dāng)中主要有三種不同的線程之間的互斥方式。下面就來和大家來討論一下OpenMP當(dāng)中的互斥操作,需要的可以參考一下

前言

在本篇文章當(dāng)中主要給大家介紹 OpenMP 當(dāng)中線程的同步和互斥機(jī)制,在 OpenMP 當(dāng)中主要有三種不同的線程之間的互斥方式:

  • 使用 critical 子句,使用這個(gè)子句主要是用于創(chuàng)建臨界區(qū)和 OpenMP 提供的運(yùn)行時(shí)庫函數(shù)的作用是一致的,只不過這種方法是直接通過編譯指導(dǎo)語句實(shí)現(xiàn)的,更加方便一點(diǎn),加鎖和解鎖的過程編譯器會(huì)幫我們實(shí)現(xiàn)。
  • 使用 atomic 指令,這個(gè)主要是通過原子指令,主要是有處理器提供的一些原子指令實(shí)現(xiàn)的。
  • OpenMP 給我們提供了 omp_lock_t 和 omp_nest_lock_t 兩種數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)簡單鎖和可重入鎖。

在本篇文章當(dāng)中主要討論 OpenMP 當(dāng)中的互斥操作,在下一篇文章當(dāng)中主要討論 OpenMP 當(dāng)中原子操作的實(shí)現(xiàn)原理,并且查看程序編譯之后的匯編指令。

自定義線程之間的同步 barrier

在實(shí)際的寫程序的過程當(dāng)中我們可能會(huì)有一種需求就是需要等待所有的線程都執(zhí)行完成之才能夠進(jìn)行后面的操作,這個(gè)時(shí)候我們就可以自己使用 barrier 來實(shí)現(xiàn)這個(gè)需求了。

比如我們要實(shí)現(xiàn)下面的一個(gè)計(jì)算式:

現(xiàn)在我們計(jì)算 n = 16 的時(shí)候上面的表達(dá)式的值:

#include <stdio.h>
#include <omp.h>
 
int factorial(int n)
{
   int s = 1;
   for(int i = 1; i <= n; ++i)
   {
      s *= i;
   }
   return s;
}
 
int main()
{
   int data[16];
#pragma omp parallel num_threads(16) default(none) shared(data)
   {
      int id = omp_get_thread_num();
      data[id] = factorial(id + 1);
      // 等待上面所有的線程都完成的階乘的計(jì)算
#pragma omp barrier
      long sum = 0;
#pragma omp single
      {
         for(int i = 0; i < 16; ++i)
         {
            sum += data[i];
         }
         printf("final value = %lf\n", (double) sum / 16);
      }
   }
   return 0;
}

在上面的代碼當(dāng)中我們首先讓 16 個(gè)線程都計(jì)算完成對應(yīng)的階乘結(jié)果之后然后在求和進(jìn)行除法操作,因此在進(jìn)行除法操作之前就需要將所有的階乘計(jì)算完成,在這里我們就可以使用 #pragma omp barrier 讓所有的線程到達(dá)這個(gè)同步點(diǎn)之后才繼續(xù)完成后執(zhí)行,這樣就保證了在進(jìn)行后面的任務(wù)的時(shí)候所有線程計(jì)算階乘的任務(wù)已經(jīng)完成。

定義臨界區(qū) critical

在并發(fā)程序當(dāng)中我們經(jīng)常會(huì)有這樣的需求,比如不同的線程需要對同一個(gè)數(shù)據(jù)進(jìn)行求和操作,當(dāng)然這個(gè)操作我們也可以通過 atomic constuct 來完成,但是在本篇文章當(dāng)中我們使用臨界區(qū)來完成,在下一篇完成當(dāng)中我們將仔細(xì)分析 OpenMP 當(dāng)中的原子操作。

比如我們現(xiàn)在有一個(gè)數(shù)據(jù) data,然后每個(gè)線程需要對這個(gè)數(shù)據(jù)進(jìn)行加操作。

#include <stdio.h>
#include <omp.h>
#include <unistd.h>
 
int main() {
   int data = 0;
#pragma omp parallel num_threads(10) shared(data) default(none)
   {
#pragma omp critical
      {
         data++;
      }
   }
   printf("data = %d\n", data);
   return 0;
}

在上面的 critical 構(gòu)造當(dāng)中我們執(zhí)行了 data ++ 這條語句,如果我們不使用 critical construct 的話,那么就可能兩個(gè)線程同時(shí)操作 data++ 這條語句,那么就會(huì)造成結(jié)果的不正確性,因?yàn)槿绻麅蓚€(gè)線程同時(shí)讀取 data 的值等于 0,然后兩個(gè)線程同時(shí)進(jìn)行++操作讓 data 的值都變成 1,再寫回,那么 data 的最終結(jié)果將會(huì)是 1,但是我們期望的結(jié)果是兩個(gè)線程進(jìn)行相加操作之后值變成 2,這就不對了,因此我們需要使用 critical construct 保證同一時(shí)刻只能夠有一個(gè)線程進(jìn)行 data++ 操作。

我們知道臨界區(qū)的實(shí)現(xiàn)是使用鎖實(shí)現(xiàn)的,當(dāng)我們使用 #pragma omp critical 的時(shí)候,我們默認(rèn)是使用的 OpenMP 內(nèi)部的默認(rèn)鎖實(shí)現(xiàn)的,如果你在其他地方也使用 #pragma omp critical 的話使用的也是同一把鎖,因此即使你用 #pragma omp critical 創(chuàng)建多個(gè)臨界區(qū)你使用的也是同一把鎖,也就是說這多個(gè)臨界區(qū)在同一時(shí)刻也只會(huì)有一個(gè)線程在一個(gè)臨界區(qū)執(zhí)行,其余的臨界區(qū)是沒有線程在執(zhí)行的,因?yàn)樗械呐R界區(qū)使用同一把鎖,而一個(gè)時(shí)刻只能夠有一個(gè)線程獲得鎖。

為了解決上面所談到的問題,在 OpenMP 當(dāng)中使用 critical 構(gòu)造代碼塊的時(shí)候我們可以指定一個(gè)名字,以此用不同的鎖在不同的臨界區(qū)。

我們現(xiàn)在對上面的情況進(jìn)行驗(yàn)證,在下面的程序當(dāng)中一共有 4 個(gè) section ,首先我們需要知道的是不同的 section 同一個(gè)時(shí)刻可以被不同的線程執(zhí)行的,每一個(gè)線程只會(huì)被執(zhí)行一次,如果有線程執(zhí)行過了,那么它將不會(huì)再被執(zhí)行。

#include <stdio.h>
#include <omp.h>
#include <unistd.h>
 
int main()
{
 
#pragma omp parallel num_threads(4) default(none)
   {
#pragma omp sections
      {
#pragma omp section
         {
#pragma omp critical
            {
               printf("tid = %d time stamp = %lf\n", omp_get_thread_num(), omp_get_wtime());
               sleep(2);
            }
         }
 
#pragma omp section
         {
#pragma omp critical
            {
               printf("tid = %d time stamp = %lf\n", omp_get_thread_num(), omp_get_wtime());
               sleep(2);
            }
         }
 
#pragma omp section
         {
#pragma omp critical
            {
               printf("tid = %d time stamp = %lf\n", omp_get_thread_num(), omp_get_wtime());
               sleep(2);
            }
         }
 
#pragma omp section
         {
#pragma omp critical
            {
               printf("tid = %d time stamp = %lf\n", omp_get_thread_num(), omp_get_wtime());
               sleep(2);
            }
         }
      }
   }
   return 0;
}

上面的程序輸出結(jié)果如下所示:

tid = 3 time stamp = 22875738.972305
tid = 0 time stamp = 22875740.972508
tid = 2 time stamp = 22875742.974888
tid = 1 time stamp = 22875744.975045

從上面程序的輸出結(jié)果我們可以知道,每一次程序的輸出都間隔了 2 秒,這就說明了,所有的打印都是在等之前的線程執(zhí)行完成之后才執(zhí)行的,這也就從側(cè)面說明了,同一個(gè)時(shí)刻只能夠有一個(gè)線程獲取到鎖,因?yàn)槭褂玫氖?#pragma omp critical 所有的臨界區(qū)都是用同一個(gè)鎖——默認(rèn)鎖。

現(xiàn)在我們修改上面的程序,每一個(gè) critical construct 都使用一個(gè)名字進(jìn)行修飾,讓每一個(gè)臨界區(qū)使用的鎖不同:

#include <stdio.h>
#include <omp.h>
#include <unistd.h>
 
int main()
{
 
#pragma omp parallel num_threads(4) default(none)
   {
#pragma omp sections
      {
#pragma omp section
         {
#pragma omp critical(A)
            {
               printf("tid = %d time stamp = %lf\n", omp_get_thread_num(), omp_get_wtime());
               sleep(2);
            }
         }
#pragma omp section
         {
#pragma omp critical(B)
            {
               printf("tid = %d time stamp = %lf\n", omp_get_thread_num(), omp_get_wtime());
               sleep(2);
            }
         }
 
#pragma omp section
         {
#pragma omp critical(C)
            {
               printf("tid = %d time stamp = %lf\n", omp_get_thread_num(), omp_get_wtime());
               sleep(2);
            }
         }
 
#pragma omp section
         {
#pragma omp critical(D)
            {
               printf("tid = %d time stamp = %lf\n", omp_get_thread_num(), omp_get_wtime());
               sleep(2);
            }
         }
      }
   }
   return 0;
}

上面的程序的輸出結(jié)果如下所示:

tid = 1 time stamp = 22876121.253737
tid = 3 time stamp = 22876121.253737
tid = 0 time stamp = 22876121.253737
tid = 2 time stamp = 22876121.253754

從上面程序的輸出結(jié)果來看,幾乎在同一個(gè)時(shí)刻所有的 printf 語句被執(zhí)行。也就是說這些臨界區(qū)之間并不互斥,這也就說名了不同的臨界區(qū)使用的鎖是不同的。

深入理解 barrier

在上一小節(jié)當(dāng)中我們提到了 critical 可以使用一個(gè)名字進(jìn)行命名,那么就可以使得不同的臨界區(qū)使用不同的鎖,這樣可以提高程序的執(zhí)行效率。那么在 OpenMP 當(dāng)中是否共享 barrier ,我們在前面介紹了 #pragma omp barrier 是否是全局所有的線程共享使用的呢?答案是不共享,因此 barrier 不需要指定名字,我們在使用 barrier 的時(shí)候每個(gè)并行域的線程組都有一個(gè)自己的 barrier 。我們可以通過下面的程序進(jìn)行分析。

#include <stdio.h>
#include <omp.h>
#include <unistd.h>
 
int main()
{
   omp_set_nested(1);
#pragma omp parallel num_threads(2) default(none)
   {
      int parent_id = omp_get_thread_num();
      printf("tid = %d\n", parent_id);
      sleep(1);
#pragma omp barrier
#pragma omp parallel num_threads(2) shared(parent_id) default(none)
      {
         sleep(parent_id + 1);
         printf("parent_id = %d tid = %d\n", parent_id, omp_get_thread_num());
#pragma omp barrier
         printf("after barrier : parent_id = %d tid = %d\n", parent_id, omp_get_thread_num());
      }
   }
   return 0;
}

上面的程序其中的一個(gè)輸出如下所示:

tid = 0
tid = 1
parent_id = 0 tid = 0
parent_id = 0 tid = 1
after barrier : parent_id = 0 tid = 0
after barrier : parent_id = 0 tid = 1
parent_id = 1 tid = 0
parent_id = 1 tid = 1
after barrier : parent_id = 1 tid = 0
after barrier : parent_id = 1 tid = 1

根據(jù)上面的程序輸出結(jié)果我們可以知道,首先 omp_set_nested(1) 啟動(dòng)并行嵌套,外部并行域有兩個(gè)線程,這兩個(gè)線程回分別創(chuàng)建兩個(gè)新的并行域,每個(gè)并行域里面都會(huì)有一個(gè)新的線程組,每個(gè)線程組都會(huì)有屬于自己的 barrier 變量,也就是說和其他的線程組中的 barrier 是無關(guān)的,因此當(dāng)并行域2中的兩個(gè)線程都到達(dá) barrier 之后就會(huì)立馬執(zhí)行最后一個(gè) printf 語句,而不需要等待并行域3中的線程 sleep 完成,而上面的程序的輸出結(jié)果也印證了這一點(diǎn)。在上面的代碼當(dāng)中并行域2中的線程只需要 sleep 1 秒,并行域3中的線程需要 sleep 2 秒,因此并行域2中的線程會(huì)先打印,并行域3中的線程會(huì)后打印。

根據(jù)上面的分析和圖解大致說明了上面的關(guān)于 barrier 代碼的執(zhí)行流程,更多關(guān)于 barrier 的實(shí)現(xiàn)細(xì)節(jié)我們在后面進(jìn)行 OpenMP 源碼分析的時(shí)候再進(jìn)行分析。

master construct

在 OpenMP 當(dāng)中還有一個(gè)比較實(shí)用的指令 master 這個(gè)指令的含義主要是代碼塊只有 master 線程才會(huì)執(zhí)行,其余線程都不會(huì)執(zhí)行。所謂 master 線程就是一個(gè)線程組當(dāng)中線程號等于 0 的線程。

你可能會(huì)覺得這個(gè)和 single 比較相似,但是和 single 不同的是這個(gè)指令最后并沒有一個(gè)同步點(diǎn),而 single 會(huì)有一個(gè)隱藏的同步點(diǎn),只有所有的線程到同步點(diǎn)之后線程才會(huì)繼續(xù)往后執(zhí)行,我們分析下面的代碼。

#include <stdio.h>
#include <omp.h>
#include <unistd.h>
 
int main()
{
#pragma omp parallel num_threads(4) default(none)
  {
#pragma omp master
    {
      sleep(1);
      printf("In master construct tid = %d timestamp = %lf\n", omp_get_thread_num(), omp_get_wtime());
    }
    printf("Out master construct tid = %d timestamp = %lf\n", omp_get_thread_num(), omp_get_wtime());
  }
  return 0;
}

上面的程序的輸出結(jié)果如下所示:

Out master construct tid = 3 timestamp = 22892756.871450
Out master construct tid = 2 timestamp = 22892756.871457
Out master construct tid = 1 timestamp = 22892756.871494
In master construct tid = 0 timestamp = 22892757.871576
Out master construct tid = 0 timestamp = 22892757.871614

從上面的輸出結(jié)果我們可以看到,非 master 線程的時(shí)間戳幾乎是一樣的也就是說他們幾乎是同時(shí)運(yùn)行的,而 master 線程則是 sleep 1 秒之后才進(jìn)行輸出的,而且 master 中的語句只有 master 線程執(zhí)行,這也就印證了我們所談到的內(nèi)容。

single construct

在使用 OpenMP 的時(shí)候,可能會(huì)有一部分代碼我們只需要一個(gè)線程去執(zhí)行,這個(gè)時(shí)候我們可以時(shí)候 single 指令,single 代碼塊只會(huì)有一個(gè)線程執(zhí)行,并且在 single 代碼塊最后會(huì)有一個(gè)同步點(diǎn),只有 single 代碼塊執(zhí)行完成之后,所有的線程才會(huì)繼續(xù)往后執(zhí)行。我們現(xiàn)在來分析一下下面的程序:

#include <stdio.h>
#include <omp.h>
#include <unistd.h>
 
int main()
{
#pragma omp parallel num_threads(4) default(none)
  {
    double start = omp_get_wtime();
#pragma omp single
    {
      printf("In single tid = %d ", omp_get_thread_num());
      sleep(5);
      printf("cost time = %lf\n", omp_get_wtime() - start);
    }
 
    printf("Out single tid = %d cost time = %lf\n", omp_get_thread_num(), omp_get_wtime() - start);
  }
  return 0;
}

上面的程序的輸出結(jié)果如下所示:

In single tid = 3 cost time = 5.000174
Out single tid = 3 cost time = 5.000229
Out single tid = 0 cost time = 5.000223
Out single tid = 2 cost time = 5.002116
Out single tid = 1 cost time = 5.002282

從上面的程序的輸出結(jié)果我們可以看到,所有的打印語句輸出的時(shí)候和 start 都相差了差不多 5 秒鐘的時(shí)間,這主要是因?yàn)樵?single 代碼塊當(dāng)中線程 sleep 了 5 秒中。雖然只有一個(gè)線程執(zhí)行 single 代碼塊,但是我們可以看到所有的線程都話費(fèi)了 5 秒鐘,這正是因?yàn)樵?single 代碼塊之后會(huì)有一個(gè)隱藏的同步點(diǎn),只有并行域中所有的代碼到達(dá)同步點(diǎn)之后,線程才能夠繼續(xù)往后執(zhí)行。

ordered construct

odered 指令主要是用于 for 循環(huán)當(dāng)中的代碼塊必須按照循環(huán)的迭代次序來執(zhí)行。因?yàn)樵谘h(huán)當(dāng)中有些區(qū)域是可以并行處理的,但是我們的業(yè)務(wù)需要在某些代碼串行執(zhí)行(這里所談到的串行執(zhí)行的意思是按照循環(huán)的迭代次序,比如說 for(int i = 0; i < 10; ++i) 這個(gè)次序就是必須按照 i 從 0 到 9 的次序執(zhí)行代碼),這樣才能夠保證邏輯上的正確性。

比如下面的例子:

#include <stdio.h>
#include <omp.h>
 
int main()
{
 
#pragma omp parallel num_threads(4) default(none)
  {
#pragma omp for ordered
    for(int i = 0; i < 8; ++i)
    {
#pragma omp ordered
      printf("i = %d ", i);
    }
  }
  return 0;
}

上面的程序的輸出結(jié)果如下所示:

i = 0 i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 

上面的程序的輸出結(jié)果一定是上面的樣子,絕對不會(huì)發(fā)生任何順序上的變化,這正是因?yàn)?ordered 的效果,他保證了線程的執(zhí)行順序必須按照循環(huán)迭代次序來。

OpenMP 中的線程同步機(jī)制

在這一小節(jié)當(dāng)中主要分析 OpenMP 當(dāng)中的一些構(gòu)造語句中的同步關(guān)系—— single, sections, for ,并且消除這些指令造成的線程之間的同步。

Sections 使用 nowait

在 OpenMP 當(dāng)中 sections 主要是使不同的線程同時(shí)執(zhí)行不同的代碼塊,但是在每個(gè) #pragma omp sections 區(qū)域之后有一個(gè)隱藏的同步代碼塊,也就是說只有所有的 section 被執(zhí)行完成之后,并且所有的線程都到達(dá)同步點(diǎn),線程才能夠繼續(xù)執(zhí)行,比如在下面的代碼當(dāng)中,printf("tid = %d finish sections\n", omp_get_thread_num()) 語句只有前面的 sections 塊全部被執(zhí)行完成,所有的線程才會(huì)開始執(zhí)行這條語句,根據(jù)這一點(diǎn)在上面的 printf 語句執(zhí)行之前所有的 section 當(dāng)中的語句都會(huì)被執(zhí)行。

#include <omp.h>
#include <stdio.h>
#include <unistd.h>
 
int main()
{
#pragma omp parallel num_threads(4) default(none)
   {
#pragma omp sections
      {
#pragma omp section
         {
            int s = omp_get_thread_num() + 1;
            sleep(s);
            printf("tid = %d sleep %d seconds\n", s, s);
         }
#pragma omp section
         {
            int s = omp_get_thread_num() + 1;
            sleep(s);
            printf("tid = %d sleep %d seconds\n", s, s);
         }
#pragma omp section
         {
            int s = omp_get_thread_num() + 1;
            sleep(s);
            printf("tid = %d sleep %d seconds\n", s, s);
         }
#pragma omp section
         {
            int s = omp_get_thread_num() + 1;
            sleep(s);
            printf("tid = %d sleep %d seconds\n", s, s);
         }
      }
 
      printf("tid = %d finish sections\n", omp_get_thread_num());
   }
   return 0;
}

上面的代碼其中的一種輸出結(jié)果如下所示:

tid = 1 sleep 1 seconds
tid = 2 sleep 2 seconds
tid = 3 sleep 3 seconds
tid = 4 sleep 4 seconds
tid = 3 finish sections
tid = 2 finish sections
tid = 0 finish sections
tid = 1 finish sections

上面的輸出結(jié)果是符合我們的預(yù)期的,所有的 section 中的 printf 語句打印在最后一個(gè) printf前面,這是因?yàn)?sections 塊之后又一個(gè)隱藏的同步點(diǎn),只有所有的線程達(dá)到同步點(diǎn)之后程序才會(huì)繼續(xù)往后執(zhí)行。

從上面的分析來看,很多時(shí)候我們是不需要一個(gè)線程執(zhí)行完成之后等待其他線程的,也就是說如果一個(gè)線程的 section 執(zhí)行完成之后而且沒有其他的 section 沒有被執(zhí)行,那么我們就不必讓這個(gè)線程掛起繼續(xù)執(zhí)行后面的任務(wù),在這種情況下我們就可以使用 nowait ,使用的編譯指導(dǎo)語句是 #pragma omp sections nowait ,具體的代碼如下所示:

#include <omp.h>
#include <stdio.h>
#include <unistd.h>
 
int main()
{
#pragma omp parallel num_threads(4) default(none)
   {
#pragma omp sections nowait
      {
#pragma omp section
         {
            int s = omp_get_thread_num() + 1;
            sleep(s);
            printf("tid = %d sleep %d seconds\n", s, s);
         }
#pragma omp section
         {
            int s = omp_get_thread_num() + 1;
            sleep(s);
            printf("tid = %d sleep %d seconds\n", s, s);
         }
#pragma omp section
         {
            int s = omp_get_thread_num() + 1;
            sleep(s);
            printf("tid = %d sleep %d seconds\n", s, s);
         }
#pragma omp section
         {
            int s = omp_get_thread_num() + 1;
            sleep(s);
            printf("tid = %d sleep %d seconds\n", s, s);
         }
      }
 
      printf("tid = %d finish sections\n", omp_get_thread_num());
   }
   return 0;
}

上面的程序的輸出結(jié)果如下所示:

tid = 1 sleep 1 seconds
tid = 0 finish sections
tid = 2 sleep 2 seconds
tid = 1 finish sections
tid = 3 sleep 3 seconds
tid = 2 finish sections
tid = 4 sleep 4 seconds
tid = 3 finish sections

從上面的輸出結(jié)果我們可以看到,當(dāng)一個(gè)線程的 section 代碼執(zhí)行完成之后,這個(gè)線程就立即執(zhí)行最后的 printf 語句了,也就是說執(zhí)行完成之后并沒有等待其他的線程,這就是我們想要的效果。

Single 使用 nowait

在 OpenMP 當(dāng)中使用 single 指令表示只有一個(gè)線程執(zhí)行 single 當(dāng)中的代碼,但是需要了解的是在 single 代碼塊最后 OpenMP 也會(huì)幫我們生成一個(gè)隱藏的同步點(diǎn),只有執(zhí)行 single 代碼塊的線程執(zhí)行完成之后,所有的線程才能夠繼續(xù)往后執(zhí)行。比如下面的示例程序:

#include <stdio.h>
#include <omp.h>
#include <unistd.h>
 
int main()
{
   double start = omp_get_wtime();
#pragma omp parallel num_threads(4) default(none) shared(start)
   {
#pragma omp single
      sleep(5);
      printf("tid = %d spent %lf s\n", omp_get_thread_num(), omp_get_wtime() - start);
   }
   double end = omp_get_wtime();
   printf("execution time : %lf", end - start);
   return 0;
}

在上面的代碼當(dāng)中啟動(dòng)了 4 個(gè)線程,在 single 的代碼塊當(dāng)中需要 sleep 5秒鐘,因?yàn)樯厦娴拇a不帶 nowait,因此雖然之后一個(gè)線程執(zhí)行 sleep(5),但是因?yàn)槠渌木€程需要等待這個(gè)線程執(zhí)行完成,因此所有的線程都需要等待 5 秒。因此可以判斷上面的代碼輸出就是每個(gè)線程輸出的時(shí)間差都是 5 秒左右。具體的上面的代碼執(zhí)行結(jié)果如下所示:

tid = 2 spent 5.002628 s
tid = 3 spent 5.002631 s
tid = 0 spent 5.002628 s
tid = 1 spent 5.005032 s
execution time : 5.005076

從上面的輸出結(jié)果來看正符合我們的預(yù)期,每個(gè)線程花費(fèi)的時(shí)間都是 5 秒左右。

現(xiàn)在我們使用 nowait 那么當(dāng)一個(gè)線程執(zhí)行 single 代碼塊的時(shí)候,其他線程就不需要進(jìn)行等待了,那么每個(gè)線程花費(fèi)的時(shí)間就非常少。我們看下面的使用 nowait 的程序的輸出結(jié)果:

#include <stdio.h>
#include <omp.h>
#include <unistd.h>
 
int main()
{
   double start = omp_get_wtime();
#pragma omp parallel num_threads(4) default(none) shared(start)
   {
#pragma omp single nowait
      sleep(5);
      printf("tid = %d spent %lf s\n", omp_get_thread_num(), omp_get_wtime() - start);
   }
   double end = omp_get_wtime();
   printf("execution time : %lf", end - start);
   return 0;
}

上面的代碼執(zhí)行結(jié)果如下所示:

tid = 2 spent 0.002375 s
tid = 0 spent 0.003188 s
tid = 1 spent 0.003202 s
tid = 3 spent 5.002462 s
execution time : 5.002538

可以看到的是線程 3 執(zhí)行了 single 代碼塊但是其他的線程并沒有執(zhí)行,而我們也使用了 nowait 因此每個(gè)線程花費(fèi)的時(shí)間會(huì)非常少,這也是符合我們的預(yù)期。

For 使用 nowait

for 的原理其實(shí)和上面兩個(gè)使用方式也是一樣的,都是不需要在同步點(diǎn)進(jìn)行同步,然后直接執(zhí)行后面的代碼。話不多說直接看代碼

#include <stdio.h>
#include <omp.h>
#include <unistd.h>
 
int main()
{
   double start = omp_get_wtime();
#pragma omp parallel num_threads(4) default(none) shared(start)
   {
#pragma omp for
      for(int i = 0; i < 4; ++i)
      {
         sleep(i);
      }
      printf("tid = %d spent %lf s\n", omp_get_thread_num(), omp_get_wtime() - start);
   }
   double end = omp_get_wtime();
   printf("execution time : %lf", end - start);
   return 0;
}

在上面的程序當(dāng)中啟動(dòng)的一個(gè) for 循環(huán),有四個(gè)線程去執(zhí)行這個(gè)循環(huán),按照默認(rèn)的調(diào)度方式第 i 個(gè)線程對應(yīng)的 i 的值就是等于 i 也就是說,最長的一個(gè)線程 sleep 的時(shí)間為 3 秒,但是 sleep 1 秒或者 2 秒和 3 秒的線程需要進(jìn)行等待,因此上面的程序的輸出結(jié)果大概都是 3 秒左右。具體的結(jié)果如下圖所示:

tid = 0 spent 3.003546 s
tid = 1 spent 3.003549 s
tid = 2 spent 3.003558 s
tid = 3 spent 3.003584 s
execution time : 3.005994

現(xiàn)在如果我們使用 nowait 那么線程不需要進(jìn)行等待,那么線程的話費(fèi)時(shí)間大概是 0 秒 1 秒 2 秒 3 秒。

#include <stdio.h>
#include <omp.h>
#include <unistd.h>
 
int main()
{
   double start = omp_get_wtime();
#pragma omp parallel num_threads(4) default(none) shared(start)
   {
#pragma omp for nowait
      for(int i = 0; i < 4; ++i)
      {
         sleep(i);
      }
      printf("tid = %d spent %lf s\n", omp_get_thread_num(), omp_get_wtime() - start);
   }
   double end = omp_get_wtime();
   printf("execution time : %lf", end - start);
   return 0;
}

查看下面的結(jié)果,也是符號我們的預(yù)期的,因?yàn)榫€程之間不需要進(jìn)行等待了。

tid = 0 spent 0.002358 s
tid = 1 spent 1.004497 s
tid = 2 spent 2.002433 s
tid = 3 spent 3.002427 s
execution time : 3.002494

總結(jié)

在本篇文章當(dāng)中主要給大家介紹了一些經(jīng)常使用的 OpenMP 用于線程之間同步的指令,并且用實(shí)際例子分析它內(nèi)部的工作機(jī)制,以及我們改如何使用 nowait 優(yōu)化程序的性能

以上就是詳解OpenMP的線程同步機(jī)制的詳細(xì)內(nèi)容,更多關(guān)于OpenMP線程同步機(jī)制的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C++通過循環(huán)實(shí)現(xiàn)猜數(shù)字小游戲

    C++通過循環(huán)實(shí)現(xiàn)猜數(shù)字小游戲

    這篇文章主要為大家詳細(xì)介紹了C++通過循環(huán)實(shí)現(xiàn)猜數(shù)字小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-09-09
  • C++中的string庫函數(shù)常見函數(shù)的作用和使用方法

    C++中的string庫函數(shù)常見函數(shù)的作用和使用方法

    這篇文章主要介紹了C++中的string庫函數(shù)常見函數(shù)的作用和使用方法,庫函數(shù)的靈活應(yīng)用是程序員的一大重要技能,本文通過實(shí)例實(shí)例代碼給大家講解的非常詳細(xì),需要的朋友可以參考下
    2022-04-04
  • 使用C語言實(shí)現(xiàn)本地socke通訊的方法

    使用C語言實(shí)現(xiàn)本地socke通訊的方法

    這篇文章主要介紹了?使用C語言實(shí)現(xiàn)本地socke通訊,代碼分為服務(wù)器代碼和客戶端代碼,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-12-12
  • C++實(shí)現(xiàn)將數(shù)組中的值反轉(zhuǎn)

    C++實(shí)現(xiàn)將數(shù)組中的值反轉(zhuǎn)

    這里給大家分享的事一則C++實(shí)現(xiàn)將數(shù)組中的值反轉(zhuǎn)的代碼,取材自《C++程序設(shè)計(jì)》(梁勇著第三版367頁),有需要的小伙伴可以參考下
    2016-05-05
  • C++ LeeCode題目:比特位計(jì)數(shù)和買賣股票的最佳時(shí)機(jī)

    C++ LeeCode題目:比特位計(jì)數(shù)和買賣股票的最佳時(shí)機(jī)

    這篇文章主要介紹了基于C語言計(jì)算比特位計(jì)數(shù)和買賣股票的最佳時(shí)機(jī),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2021-07-07
  • C++中使用哈希表(unordered_map)的一些常用操作方法

    C++中使用哈希表(unordered_map)的一些常用操作方法

    C++標(biāo)準(zhǔn)庫中使用的unordered_map底層實(shí)現(xiàn)是哈希表,下面這篇文章主要給大家介紹了關(guān)于C++中使用哈希表(unordered_map)的一些常用操作方法,需要的朋友可以參考下
    2022-03-03
  • C++實(shí)現(xiàn)關(guān)系與關(guān)系矩陣的代碼詳解

    C++實(shí)現(xiàn)關(guān)系與關(guān)系矩陣的代碼詳解

    這篇文章主要介紹了C++實(shí)現(xiàn)關(guān)系與關(guān)系矩陣,功能實(shí)現(xiàn)包括關(guān)系的矩陣表示,關(guān)系的性質(zhì)判斷及關(guān)系的合成,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-04-04
  • linux之sed命令的用法

    linux之sed命令的用法

    sed是一個(gè)很好的文件處理工具,本身是一個(gè)管道命令,主要是以行為單位進(jìn)行處理,可以將數(shù)據(jù)行進(jìn)行替換、刪除、新增、選取等特定工作,下面先了解一下sed的用法
    2013-10-10
  • win10+VS2017+Cuda10.0環(huán)境配置詳解

    win10+VS2017+Cuda10.0環(huán)境配置詳解

    這篇文章主要介紹了win10+VS2017+Cuda10.0環(huán)境配置詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • C++實(shí)現(xiàn)旋轉(zhuǎn)數(shù)組的二分查找

    C++實(shí)現(xiàn)旋轉(zhuǎn)數(shù)組的二分查找

    這篇文章主要介紹了C++實(shí)現(xiàn)旋轉(zhuǎn)數(shù)組的二分查找方法,涉及數(shù)組的操作,有值得借鑒的技巧,需要的朋友可以參考下
    2014-09-09

最新評論

苏尼特右旗| 娱乐| 屏山县| 富锦市| 滨州市| 商城县| 潜山县| 五大连池市| 达日县| 武安市| 开平市| 霍州市| 闽清县| 宝应县| 嘉峪关市| 彭泽县| 平武县| 砀山县| 灵丘县| 肇源县| 南宁市| 长岭县| 八宿县| 思茅市| 米脂县| 淮安市| 凤山县| 泽库县| 栖霞市| 嵊泗县| 孙吴县| 伊吾县| 阿克| 张家港市| 谢通门县| 深州市| 务川| 榆林市| 通道| 石门县| 阿勒泰市|