详解OpenMP的线程同步机制

目录
  • 前言
  • 自定义线程之间的同步 barrier
  • 定义临界区 critical
  • 深入理解 barrier
  • master construct
  • single construct
  • ordered construct
  • OpenMP 中的线程同步机制
    • Sections 使用 nowait
    • Single 使用 nowait
    • For 使用 nowait
  • 总结

前言

在本篇文章当中主要给大家介绍 OpenMP 当中线程的同步和互斥机制,在 OpenMP 当中主要有三种不同的线程之间的互斥方式:

  • 使用 critical 子句,使用这个子句主要是用于创建临界区和 OpenMP 提供的运行时库函数的作用是一致的,只不过这种方法是直接通过编译指导语句实现的,更加方便一点,加锁和解锁的过程编译器会帮我们实现。
  • 使用 atomic 指令,这个主要是通过原子指令,主要是有处理器提供的一些原子指令实现的。
  • OpenMP 给我们提供了 omp_lock_t 和 omp_nest_lock_t 两种数据结构实现简单锁和可重入锁。

在本篇文章当中主要讨论 OpenMP 当中的互斥操作,在下一篇文章当中主要讨论 OpenMP 当中原子操作的实现原理,并且查看程序编译之后的汇编指令。

自定义线程之间的同步 barrier

在实际的写程序的过程当中我们可能会有一种需求就是需要等待所有的线程都执行完成之才能够进行后面的操作,这个时候我们就可以自己使用 barrier 来实现这个需求了。

比如我们要实现下面的一个计算式:

现在我们计算 n = 16 的时候上面的表达式的值:

#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);
      // 等待上面所有的线程都完成的阶乘的计算
#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;
}

在上面的代码当中我们首先让 16 个线程都计算完成对应的阶乘结果之后然后在求和进行除法操作,因此在进行除法操作之前就需要将所有的阶乘计算完成,在这里我们就可以使用 #pragma omp barrier 让所有的线程到达这个同步点之后才继续完成后执行,这样就保证了在进行后面的任务的时候所有线程计算阶乘的任务已经完成。

定义临界区 critical

在并发程序当中我们经常会有这样的需求,比如不同的线程需要对同一个数据进行求和操作,当然这个操作我们也可以通过 atomic constuct 来完成,但是在本篇文章当中我们使用临界区来完成,在下一篇完成当中我们将仔细分析 OpenMP 当中的原子操作。

比如我们现在有一个数据 data,然后每个线程需要对这个数据进行加操作。

#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 构造当中我们执行了 data ++ 这条语句,如果我们不使用 critical construct 的话,那么就可能两个线程同时操作 data++ 这条语句,那么就会造成结果的不正确性,因为如果两个线程同时读取 data 的值等于 0,然后两个线程同时进行++操作让 data 的值都变成 1,再写回,那么 data 的最终结果将会是 1,但是我们期望的结果是两个线程进行相加操作之后值变成 2,这就不对了,因此我们需要使用 critical construct 保证同一时刻只能够有一个线程进行 data++ 操作。

我们知道临界区的实现是使用锁实现的,当我们使用 #pragma omp critical 的时候,我们默认是使用的 OpenMP 内部的默认锁实现的,如果你在其他地方也使用 #pragma omp critical 的话使用的也是同一把锁,因此即使你用 #pragma omp critical 创建多个临界区你使用的也是同一把锁,也就是说这多个临界区在同一时刻也只会有一个线程在一个临界区执行,其余的临界区是没有线程在执行的,因为所有的临界区使用同一把锁,而一个时刻只能够有一个线程获得锁。

为了解决上面所谈到的问题,在 OpenMP 当中使用 critical 构造代码块的时候我们可以指定一个名字,以此用不同的锁在不同的临界区。

我们现在对上面的情况进行验证,在下面的程序当中一共有 4 个 section ,首先我们需要知道的是不同的 section 同一个时刻可以被不同的线程执行的,每一个线程只会被执行一次,如果有线程执行过了,那么它将不会再被执行。

#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;
}

上面的程序输出结果如下所示:

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

从上面程序的输出结果我们可以知道,每一次程序的输出都间隔了 2 秒,这就说明了,所有的打印都是在等之前的线程执行完成之后才执行的,这也就从侧面说明了,同一个时刻只能够有一个线程获取到锁,因为使用的是 #pragma omp critical 所有的临界区都是用同一个锁——默认锁。

现在我们修改上面的程序,每一个 critical construct 都使用一个名字进行修饰,让每一个临界区使用的锁不同:

#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;
}

上面的程序的输出结果如下所示:

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

从上面程序的输出结果来看,几乎在同一个时刻所有的 printf 语句被执行。也就是说这些临界区之间并不互斥,这也就说名了不同的临界区使用的锁是不同的。

深入理解 barrier

在上一小节当中我们提到了 critical 可以使用一个名字进行命名,那么就可以使得不同的临界区使用不同的锁,这样可以提高程序的执行效率。那么在 OpenMP 当中是否共享 barrier ,我们在前面介绍了 #pragma omp barrier 是否是全局所有的线程共享使用的呢?答案是不共享,因此 barrier 不需要指定名字,我们在使用 barrier 的时候每个并行域的线程组都有一个自己的 barrier 。我们可以通过下面的程序进行分析。

#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;
}

上面的程序其中的一个输出如下所示:

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

根据上面的程序输出结果我们可以知道,首先 omp_set_nested(1) 启动并行嵌套,外部并行域有两个线程,这两个线程回分别创建两个新的并行域,每个并行域里面都会有一个新的线程组,每个线程组都会有属于自己的 barrier 变量,也就是说和其他的线程组中的 barrier 是无关的,因此当并行域2中的两个线程都到达 barrier 之后就会立马执行最后一个 printf 语句,而不需要等待并行域3中的线程 sleep 完成,而上面的程序的输出结果也印证了这一点。在上面的代码当中并行域2中的线程只需要 sleep 1 秒,并行域3中的线程需要 sleep 2 秒,因此并行域2中的线程会先打印,并行域3中的线程会后打印。

根据上面的分析和图解大致说明了上面的关于 barrier 代码的执行流程,更多关于 barrier 的实现细节我们在后面进行 OpenMP 源码分析的时候再进行分析。

master construct

在 OpenMP 当中还有一个比较实用的指令 master 这个指令的含义主要是代码块只有 master 线程才会执行,其余线程都不会执行。所谓 master 线程就是一个线程组当中线程号等于 0 的线程。

你可能会觉得这个和 single 比较相似,但是和 single 不同的是这个指令最后并没有一个同步点,而 single 会有一个隐藏的同步点,只有所有的线程到同步点之后线程才会继续往后执行,我们分析下面的代码。

#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;
}

上面的程序的输出结果如下所示:

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

从上面的输出结果我们可以看到,非 master 线程的时间戳几乎是一样的也就是说他们几乎是同时运行的,而 master 线程则是 sleep 1 秒之后才进行输出的,而且 master 中的语句只有 master 线程执行,这也就印证了我们所谈到的内容。

single construct

在使用 OpenMP 的时候,可能会有一部分代码我们只需要一个线程去执行,这个时候我们可以时候 single 指令,single 代码块只会有一个线程执行,并且在 single 代码块最后会有一个同步点,只有 single 代码块执行完成之后,所有的线程才会继续往后执行。我们现在来分析一下下面的程序:

#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;
}

上面的程序的输出结果如下所示:

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

从上面的程序的输出结果我们可以看到,所有的打印语句输出的时候和 start 都相差了差不多 5 秒钟的时间,这主要是因为在 single 代码块当中线程 sleep 了 5 秒中。虽然只有一个线程执行 single 代码块,但是我们可以看到所有的线程都话费了 5 秒钟,这正是因为在 single 代码块之后会有一个隐藏的同步点,只有并行域中所有的代码到达同步点之后,线程才能够继续往后执行。

ordered construct

odered 指令主要是用于 for 循环当中的代码块必须按照循环的迭代次序来执行。因为在循环当中有些区域是可以并行处理的,但是我们的业务需要在某些代码串行执行(这里所谈到的串行执行的意思是按照循环的迭代次序,比如说 for(int i = 0; i < 10; ++i) 这个次序就是必须按照 i 从 0 到 9 的次序执行代码),这样才能够保证逻辑上的正确性。

比如下面的例子:

#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;
}

上面的程序的输出结果如下所示:

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

上面的程序的输出结果一定是上面的样子,绝对不会发生任何顺序上的变化,这正是因为 ordered 的效果,他保证了线程的执行顺序必须按照循环迭代次序来。

OpenMP 中的线程同步机制

在这一小节当中主要分析 OpenMP 当中的一些构造语句中的同步关系—— single, sections, for ,并且消除这些指令造成的线程之间的同步。

Sections 使用 nowait

在 OpenMP 当中 sections 主要是使不同的线程同时执行不同的代码块,但是在每个 #pragma omp sections 区域之后有一个隐藏的同步代码块,也就是说只有所有的 section 被执行完成之后,并且所有的线程都到达同步点,线程才能够继续执行,比如在下面的代码当中,printf("tid = %d finish sections\n", omp_get_thread_num()) 语句只有前面的 sections 块全部被执行完成,所有的线程才会开始执行这条语句,根据这一点在上面的 printf 语句执行之前所有的 section 当中的语句都会被执行。

#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;
}

上面的代码其中的一种输出结果如下所示:

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

上面的输出结果是符合我们的预期的,所有的 section 中的 printf 语句打印在最后一个 printf前面,这是因为 sections 块之后又一个隐藏的同步点,只有所有的线程达到同步点之后程序才会继续往后执行。

从上面的分析来看,很多时候我们是不需要一个线程执行完成之后等待其他线程的,也就是说如果一个线程的 section 执行完成之后而且没有其他的 section 没有被执行,那么我们就不必让这个线程挂起继续执行后面的任务,在这种情况下我们就可以使用 nowait ,使用的编译指导语句是 #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;
}

上面的程序的输出结果如下所示:

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

从上面的输出结果我们可以看到,当一个线程的 section 代码执行完成之后,这个线程就立即执行最后的 printf 语句了,也就是说执行完成之后并没有等待其他的线程,这就是我们想要的效果。

Single 使用 nowait

在 OpenMP 当中使用 single 指令表示只有一个线程执行 single 当中的代码,但是需要了解的是在 single 代码块最后 OpenMP 也会帮我们生成一个隐藏的同步点,只有执行 single 代码块的线程执行完成之后,所有的线程才能够继续往后执行。比如下面的示例程序:

#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;
}

在上面的代码当中启动了 4 个线程,在 single 的代码块当中需要 sleep 5秒钟,因为上面的代码不带 nowait,因此虽然之后一个线程执行 sleep(5),但是因为其他的线程需要等待这个线程执行完成,因此所有的线程都需要等待 5 秒。因此可以判断上面的代码输出就是每个线程输出的时间差都是 5 秒左右。具体的上面的代码执行结果如下所示:

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

从上面的输出结果来看正符合我们的预期,每个线程花费的时间都是 5 秒左右。

现在我们使用 nowait 那么当一个线程执行 single 代码块的时候,其他线程就不需要进行等待了,那么每个线程花费的时间就非常少。我们看下面的使用 nowait 的程序的输出结果:

#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;
}

上面的代码执行结果如下所示:

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 执行了 single 代码块但是其他的线程并没有执行,而我们也使用了 nowait 因此每个线程花费的时间会非常少,这也是符合我们的预期。

For 使用 nowait

for 的原理其实和上面两个使用方式也是一样的,都是不需要在同步点进行同步,然后直接执行后面的代码。话不多说直接看代码

#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;
}

在上面的程序当中启动的一个 for 循环,有四个线程去执行这个循环,按照默认的调度方式第 i 个线程对应的 i 的值就是等于 i 也就是说,最长的一个线程 sleep 的时间为 3 秒,但是 sleep 1 秒或者 2 秒和 3 秒的线程需要进行等待,因此上面的程序的输出结果大概都是 3 秒左右。具体的结果如下图所示:

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

现在如果我们使用 nowait 那么线程不需要进行等待,那么线程的话费时间大概是 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;
}

查看下面的结果,也是符号我们的预期的,因为线程之间不需要进行等待了。

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

总结

在本篇文章当中主要给大家介绍了一些经常使用的 OpenMP 用于线程之间同步的指令,并且用实际例子分析它内部的工作机制,以及我们改如何使用 nowait 优化程序的性能

以上就是详解OpenMP的线程同步机制的详细内容,更多关于OpenMP线程同步机制的资料请关注我们其它相关文章!

(0)

相关推荐

  • 安装OpenMPI来配合C语言程序进行并行计算

    安装OPENMPI 由于是实验,也不进行多机的配置了,只在虚拟机里安装吧.多个机器的配置可以参考此文 最简单的方法,apt安装 sudo apt-get install libcr-dev mpich2 mpich2-doc 测试 hello.c /* C Example */ #include <mpi.h> #include <stdio.h> int main (int argc, char* argv[]) { int rank, size; MPI_Init (&

  • OpenMP深入剖析reduction子句教程

    目录 前言 从并发求和开始 解决求和问题的各种办法 使用数组巧妙解决并发程序当中的数据竞争问题 reduction 子句 深入剖析 reduction 子句 加法+操作符 乘法*操作符 逻辑与&&操作符 或||操作符 MIN 最小值 MAX 最大值 & 按位与 |按位或 ^按位异或 总结 前言 在前面的教程OpenMP入门当中我们简要介绍了 OpenMP 的一些基础的使用方法,在本篇文章当中我们将从一些基础的问题开始,然后仔细介绍在 OpenMP 当中 reduction 子句的各

  • 详解CLion配置openMP的方法

    使用MinGW64在Clion中配置openMP的开发 安装MinGW64和CLion配置CMakeList.txtCLion 2020.2.3 Build #CL-202.7319.72, built on September 18, 2020 对openMP编译制导的格式问题踩坑 下载 MinGW64 CLion 创建工程,配置CMakeList.txt cmake_minimum_required(VERSION 3.17) project(openMP C) set(CMAKE_C_ST

  • OpenMP 共享内存的并行编程框架入门详解

    目录 简介 认识 openmp 的简单易用性 C 语言实现 C++ 实现 OpenMP 实现 opnemp 基本原理 积分例子 总结 简介 OpenMP 一个非常易用的共享内存的并行编程框架,它提供了一些非常简单易用的API,让编程人员从复杂的并发编程当中释放出来,专注于具体功能的实现.openmp 主要是通过编译指导语句以及他的动态运行时库实现,在本篇文章当中我们主要介绍 openmp 一些入门的简单指令的使用. 认识 openmp 的简单易用性 比如现在我们有一个任务,启动四个线程打印 he

  • 详解OpenMP的线程同步机制

    目录 前言 自定义线程之间的同步 barrier 定义临界区 critical 深入理解 barrier master construct single construct ordered construct OpenMP 中的线程同步机制 Sections 使用 nowait Single 使用 nowait For 使用 nowait 总结 前言 在本篇文章当中主要给大家介绍 OpenMP 当中线程的同步和互斥机制,在 OpenMP 当中主要有三种不同的线程之间的互斥方式: 使用 criti

  • 详解易语言线程同步

    在易语言官方多线程支持库中提供线程同步的方法是用许可区. 加入许可区之后可以防止多个线程同时访问公用变量是发生冲突.加入许可区的代码同时只能有一个线程访问,避免冲突. 创建许可区: 创建并返回一个进入许可证数值,此许可证值用作进入程序中的指定许可代码区,以避免多线程冲突.成功返回非零整数值,失败返回0.所创建的许可证在不再使用后,必须使用"删除进入许可证"命令将其删除.本命令为初级命令. 删除进入许可证: 删除由"创建进入许可证"命令所创建返回的进入许可证.成功返回

  • 详解MySQL的半同步

    前言 年后在进行腾讯二面的时候,写完算法的后问的第一个问题就是,MySQL的半同步是什么?我当时直接懵了,我以为是问的MySQL的两阶段提交的问题呢?结果确认了一下后不是两阶段提交,然后面试官看我连问的是啥都不知道,就直接跳过这个问题,直接聊下一个问题了.所以这次总结一下这部分的知识内容,文字内容比较多,可能会有些枯燥,但对于这方面感兴趣的人来说还是比较有意思的. MySQL的主从复制 我们的一般在大规模的项目上,都是使用MySQL的复制功能来创建MySQL的主从集群的.主要是可以通过为服务器配

  • 一文详解Java中的类加载机制

    目录 一.前言 二.类加载的时机 2.1 类加载过程 2.2 什么时候类初始化 2.3 被动引用不会初始化 三.类加载的过程 3.1 加载 3.2 验证 3.3 准备 3.4 解析 3.5 初始化 四.父类和子类初始化过程中的执行顺序 五.类加载器 5.1 类与类加载器 5.2 双亲委派模型 5.3 破坏双亲委派模型 六.Java模块化系统 一.前言 Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最 终形成可以被虚拟机直接使用的Java类型,这个过程

  • 详解redis集群选举机制

    概要 当redis集群的主节点故障时,Sentinel集群将从剩余的从节点中选举一个新的主节点,有以下步骤: 故障节点主观下线 故障节点客观下线 Sentinel集群选举Leader Sentinel Leader决定新主节点 选举过程 1.主观下线 Sentinel集群的每一个Sentinel节点会定时对redis集群的所有节点发心跳包检测节点是否正常.如果一个节点在down-after-milliseconds时间内没有回复Sentinel节点的心跳包,则该redis节点被该Sentinel

  • 详解Java停止线程的四种方法

    一.线程停止基础知识 interrupted(): 测试当前线程是否已经中断.该方法为静态方法,调用后会返回boolean值.不过调用之后会改变线程的状态,如果是中断状态调用的,调用之后会清除线程的中断状态. isInterrupted(): 测试线程是否已经中断.该方法由对象调用 interrupt(): 标记线程为中断状态,不过不会中断正在运行的线程. stop(): 暴力停止线程.已弃用. 二.停止线程方法1:异常法停止 线程调用interrupt()方法后,在线程的run方法中判断当前对

  • 详解Java的Exception异常机制

    一.前言 在Java中,我们在执行代码的过程中难免会遇到错误与Exception异常,可是我们一直都是锤头Coding而忽略了学习Exception这个东西!我们只是知道在发生Exception的地方让代码自动生成throw exception或者是使用try-catch括起来处理,那你了解Java的Exception吗?今天就让我们把一起来看看Java的Exception吧! 在Java中,我们的代码再出现错误的时候无非是两种情况:一是Error,一是异常Exception.如果是Error,

  • 详解_beginthreadex()创建线程

    目录 一.使用_beginthreadex() 二._beginthreadex()与代CreateThread()区别 一.使用_beginthreadex() 需要的头文件支持#include         // for _beginthread()需要的设置:ProjectàSetting-->C/C++-->User run-time library 选择Debug Multithreaded 或者Multithreaded.即使用: MT或MTD. 代码如下:      #incl

  • 详解Java的线程状态

    Java的每个线程都具有自己的状态,Thread类中成员变量threadStatus存储了线程的状态: private volatile int threadStatus = 0; 在Thread类中也定义了状态的枚举,共六种,如下: public enum State { NEW, // 新建状态 RUNNABLE, // 执行状态 BLOCKED, // 阻塞状态 WAITING, // 无限期等待状态 TIMED_WAITING, // 有限期等待状态 TERMINATED; // 退出状

  • 详解微信小程序 同步异步解决办法

    详解微信小程序 同步异步解决办法 小程序中函数体还没有完成,下一个函数就开始执行了,而且两个函数之间需要传参.那是因为微信小程序函数是异步执行的.但微信小程序增加了ES6的promise特性支持,微信小程序新版本中移除了promise的支持,需要自己使用第三方库来自行实现ES6的promise特性. WxService.js import Tools from 'Tools' import es6 from '../assets/plugins/es6-promise' class Servic

随机推荐