深入理解 OpenMP 线程同步机制

深入理解 OpenMP 线程同步机制

前言

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

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

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

自定义线程之间的同步 barrier

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

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

\[data = \frac{1! + 2! + ... + n!}{n} \]

现在我们计算 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 优化程序的性能,以上就是本篇文章的所有内容希望大家有所收获!


更多精彩内容合集可访问项目:http://github.com/Chang-LeHung/CSCore

关注公众号:一无是处的研究僧,了解更多计算机(Java、Python、计算机系统基础、算法与数据结构)知识。

本文转载于网络 如有侵权请联系删除

相关文章

  • 输入两个正整数m和n,求其最大公约数和最小公倍数。

    输入两个正整数m和n,求其最大公约数和最小公倍数。//题目:输入两个正整数m和n,求其最大公约数和最小公倍数。 //求最大公约数用辗转相除法 //最小公倍数=输入的两个数之积除于它们的最大公约数 #include intmain() { inta,b,t,r,n; printf("请输入两个数字:\n"); scanf("%d%d",&a,&b);//812 if(a<b) {t=b;b=a;a=t;}//a=12b=8 //printf("a=%db=%d\n",a,b); r=a%b;//r=4 n=a*b;//b=8*12=96两个数的乘积 //printf("r=%dn=%d",r,n); //辗转相除 while(r!=0) { a=b;//a=8 b=r;//b=4 r=a%b;//r=096/4=24 } printf("这两个数的最大公约数是%d,最小公倍数是%d\n",b,n/b); return0; }复制测试:

  • 谷歌和苹果公司的“共生关系”在英遭遇投诉;谷歌15年老臣离职创业 曾负责“下一个10亿用户”计划

    01谷歌和苹果公司的“共生关系”在英遭遇投诉据报道,一群在线广告商日前抱怨,谷歌和苹果公司的“共生关系”应该受到英国反垄断监管机构的审查。行业游说组织“MarketersforanOpenWeb”表示,已请求英国竞争与市场管理局(CMA)考虑在线广告商的担忧,即这两家公司“没有正面竞争”。确切而言,MarketersforanOpenWeb敦促CMA要求两家公司提供一些内部文件,比如美国诉讼中提到的那些文件,包括会议和各种通信中提到的内容。上周,MarketersforanOpenWe还提交了一份申请,要求CMA调查苹果的广告和网络跟踪问题。当前,CMA正在积极审查全球主要科技公司的反垄断行为,主要担心这些科技公司拥有太多权力。本月早些时候,在审查了谷歌计划中的广告政策改革后,CMA又开始对苹果的应用支付规则进行调查。美国司法部在去年提起的诉讼中曾表示,在搜索方面,谷歌和苹果的合作就像是一家公司。司法部还称,谷歌在浏览器和手机上为其搜索引擎签署的独家付费协议,违反了公平竞争法规。谷歌对此表示,其与苹果和其他公司签署的协议,模仿了其他软件分销协议,并经受住了反复的反垄断审查。这些交易并不

  • 图形化开放式生信分析系统开发 - 4 生信分析流程图形化

    在上文图形化开放式生信分析系统开发-3生信分析流程的进化讨论了生信分析pipeline的进化,从手动到自动,但仍然停留在终端命令行阶段,为了让更多非生信专业的人能够使用,就要想办法实现生信分析pipeline的图形化:提供能够快速上手的UI,简单点击鼠标就能够运行的图形化系统。核心功能:pipeline的图形化工作流设计器变量处理:要实现pipeline图形设计器,首先要先对用到的变量,做统一的设计。变量的分类:根据实际经验,pipeline变量用到根据用途可以分为以下几类:分析数据目录${data}分析过程输出目录${result}分析用到的软件bwa${tools.bwa};samtools${tools.samtools}分析流程中用的reference文件以及数据库,如hg19.fa${ref.hg19}分析流程中,用到的cutoff值.如cnv的cutoff值${cutoff.cnv}分析流程运行时配置的资源,如线程数${threads}分配内存大小${mem}变量值的类型: 字符:通用的格式,比较宽松程序:校验变量值时,判断文件是否存在,是否有可执行权限文件:校验变量值时,

  • javascript当中排序比较器用法

    例1.5(排序比较器) <!DOCTYPEHTMLPUBLIC"-//W3C//DTDHTML4.01Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <metahttp-equiv="content-type"content="text/html;charset=utf-8"/> forteach. <scripttype="text/javascript"> functionsortNumberqixy(a,b) { /*马克-to-win returnedvaluetype:Arrayobject JavaScriptsyntax:-myArray.sort() -myArray.sort(aComparator) Argumentlist:aComparatorAfunctionobjectthatwillcomparetwoi

  • JavaScript中科学计数法的问题

    JavaScript中经常会碰到数值计算问题,偶尔会在不经意间报一个不是bug的bug。今天来说说一个特殊的例子。我以0.0011BTC价格买入0.0002CZR计算出了的金额是0.00000022BTC,而JavaScript计算出来的金额是2.2e-7。值是对的,只是用了科学计数法,也是数值类型。但是问题来了,一般用户用户看不懂2.2e-7,那么就把它转换成0.00000022吧。然而问题了,我用尽办法,怎么样都无法将2.2e-7转换成直观的0.00000022。或许你会嘲笑我,告诉我直接用.toFixed()方法。但是新问题又来了,.toFixed()会保留足够的小数位,比如:2e-7.toFixed(8)得到的值是0.00000020,2e2.toFixed(8)得到的值是200.00000000。最后的0让我感到多余…问题分析问题还是要解决,只能深入了解JavaScript中科学计数法相关的知识。对于极大或者极小的数,可以用科学计数法e来表示的浮点数值来表示。科学计数法允许字母e或E的后面,跟着一个整数,表示这个数值的指数部分。以下两种情况,JavaScript会自动将数值转

  • OpenSea使用指南:作为DApp玩家,你最好知道这个|DApp101

    前几天,有群里的小伙伴们发来这样一张截图:墨鲫心里想,纳尼,1.5个ETH?这不是我的加密英雄里面的服部半藏吗?墨鲫我也有啊,竟然可以卖到1.5个ETH吗?于是墨鲫打开了这个神奇的网站,号称是“区块链游戏的eBay”的OpenSea。简单来说,OpenSea其实也就是一个去中心化的资产交易平台,但它有所不同的是,现阶段它支持的大部分是ERC721的游戏数字资产,因此也成为区块链游戏爱好者常去的网站之一。接下来墨鲫就给大家介绍一些基本的使用指南。数据来源于spiderstore在OpenSea上成为一个卖家当我们在游戏中获得比较好但有些多余的装备时,又或是不想玩弃游时,我们可以将自己的区块链游戏中的资产拿出来进行售卖。当然最开始,我们需要确认需要交易的资产在以太坊主链上。比如我的加密英雄因为使用了Loom侧链储存游戏资产,因此需要先在游戏中将资产转移到ETH网络中。当你的资产确认在以太坊主网上后,你就能够在OpenSea的我的账户里看到自己的加密资产了,而这些加密资产都是可以拿来售卖的。在OpenSea上现阶段支持四种售卖方式:一种是直接定价,比如我定价是1ETH,那么当中意的卖家看到时

  • 个人机器人教学与科研反思与计划(回归和坚守)

    迷茫了好久,不清楚当下所为之事(工作)有何意义,未来将去向何处,渐渐豁然开朗了,不是想通,而是淡然。嗯,自己的确是不称职的高校教师,缺乏经验,所用教学手段效果也不好,但是努力去改正缺点和不足。对学生影响更为深远的是方法而非内容,因为所有课堂知识点都可以在图书馆和互联网上找到,然而很多大学生毕业后,连PPT和文献检索总结的能力都不合格。今后工作重心围绕机器人教学和科研工作进行。课程围绕如下主题展开(2018版),注2019版已经更新,见更新博文:科研,以项目申请和论文发表为重点。将近10年ICRA、IROS和ROSCon会议内容粗略看一遍,2012, 2013, 2014, 2015, 2016,2017, 2018。精读RobotOperatingSystem(ROS)TheCompleteReference第1,2,3卷。机器人学.理论.编程.应用.相关书籍:https://download.csdn.net/download/zhangrelay/10322271~~~~有一些同学问及大学感觉什么也没有学到,或者学了很多不知如何用,还有只会做题呢其他都心慌慌。我自己曾经也是如此,中

  • 开创性提供量子计算机从软到硬的完整可编程性!Delft提出可执行量子计算指令集eQASM

    机器之心发布机器之心编辑部传统计算机硬件的计算单元与控制单元一样,都是数字电路,能够轻易集成在一块处理器上;传统软件输出的二进制代码则可以直接在处理器上运行。量子计算机与此不同,其计算单元是量子比特,而控制媒介是模拟信号,通常需要由独立于量子芯片的控制单元产生。额外的量子控制架构,作为量子软件与量子硬件之间的桥梁,构成了量子计算机与传统计算机在系统结构上最核心的区别之一。但在量子计算工程领域,以往量子软件与量子硬件的研究相对独立,这导致二者的设计及实现难以有机地联接在一起。去年8月,荷兰代尔夫特理工大学博士生付祥及其所在团队提出了一种针对超导量子计算机的控制微体系结构,首次有机地连接了量子软件和量子硬件,并使传统处理器的设计技术能够为量子控制处理器所用(MICRO2017最佳论文奖)。针对现有量子汇编语言信息密度低、无法在量子硬件上直接执行、难以支持量子程序流控制(如反馈控制)等问题,近日,该团队又开创性地提出了一种可执行的量子计算机指令集架构eQASM,旨在为量子软件提供一个通用灵活的硬件抽象,实现量子计算机从软件到硬件的完整可编程性。该论文已于近日被计算机体系结构顶会HPCA201

  • ASM 翻译系列第十七弹:ASM Internal ASM Disk Directory

    原作者:BaneRadulovic译者:郭旭瑞审核:魏兴华DBGeeK社群联合出品ASMDiskDirectory本篇文章讲述ASM元信息的2号文件,ASM的2号文件是ASM的磁盘目录,它跟踪磁盘组中的所有磁盘。由于在ASM中磁盘组是一个独立的存储单位,因此每一个磁盘组都会有自己的磁盘目录。译者注:ASM中每一个磁盘组都是自解释的,磁盘组之间没有任何的信息上依赖。对ASM来说,磁盘目录只是一个普通的ASM文件,在ASM的文件目录中也会有它的条目,如果磁盘组做了冗余策略,它也会相应做镜像,也会像其他文件一样根据实际需要做空间的伸长。每个磁盘目录的条目都维护了如下的一些信息:●磁盘号●磁盘的状态●磁盘的名称●所在的Failgroup名称●创建的时间戳●失败的时间戳●自失败时间戳截止目前的时间●resize目标值●磁盘修复时间●Zone的信息V$ASM_DISKview磁盘目录的大部分信息都可以通过视图V$ASM_DISK获得,每一个磁盘都在这个视图中有一行记录,这些磁盘包括哪些还不是任何磁盘组的磁盘。每次查询这个视图都会导致ASM执行磁盘的发现操作,因此查询这个视图的代价是非常大的。下面的

  • 【Techo Day腾讯技术开放日】数据仓库总结

    数据仓库总结一、数据仓库概述首先,我们先来看下数据库、数据集市、数据仓库以及数据湖的概念。1、什么是数据库?数据库(Database)是按照一定格式和数据结构在计算机保存数据的软件,属于物理层。最早期是广义上的数据库,这个阶段的数据库结构主要以层次或网状的为主,这是数据库的数据和程序间具备非常强的依赖性,应用有一定局限性。我们现在所说的数据库一般指的是关系型数据库。关系数据库是指采用了关系模型来组织数据的数据库,其以行和列的形式存储数据,具有结构化程度高,独立性强,冗余度低等优点。关系型数据库主要用于联机事务处理OLTP(On-LineTransactionProcessing),主要用于进行基本的、日常的事务处理,例如银行交易等场景。2、什么是数据集市?数据集市是一种微型的数据仓库,它通常是有更少的数据,更少的主题区域,以及更少的历史数据,如果数据仓库是企业级的,那数据集市就是部门级的,一般数据集市只能为某个局部范围内的管理人员服务。3、什么是数据仓库?数据仓库(DataWarehouse),可简写为DW或DWH。它是为企业所有级别的决策制定过程,提供所有类型数据支持的战略集合。它是

  • postgres主从切换后,原主重新加入集群报错:pg_rewind: error: could not open file

    一、用keepalived进行脚本主从切换,当原主重新加入集群报错 [root@hlcc_slavedata]#/usr/pgsql-13/bin/pg_rewind-D'/home/postgres/data'--source-server='host=172.28.17.142port=5876user=repmgrpassword=hl95repmgrdbname=repmgrconnect_timeout=2' pg_rewind:error:cannotbeexecutedby"root" Youmustrunpg_rewindasthePostgreSQLsuperuser. [root@hlcc_slavedata]#su-postgres Lastlogin:WedAug309:31:30CST2022onpts/0 [postgres@hlcc_slave~]$/usr/pgsql-13/bin/pg_rewind-D'/home/postgres/data'--source-server='host=172.28.17.142port=5876user=repmg

  • java基础-类及方法

    1、成员变量   在Java中对象的属性也称为成员变量。 为了了解成员变量,首先定义一个图书类,成员变量对应于类对象的属性, 在Book类中设置3个成员变量,分别为id、name和category,分别对应于图书编号、图书名称和图书类别3个图书属性。   /** *在java中。对象的属性也被称为成员变量 */ publicclassBook{ privateStringName;//定义一个String类型的成员变量 publicStringGetName(){//定义一个GetName方法 intid=0;//局部变量 setName("Java");//调用类中的其他方法 returnid+this.Name;//设置方法返回值 } /** *定义一个SetName方法 *@paramname */ privatevoidSetName(Stringname){ this.Name=name;//将参数值赋予类中的一个成员变量 } publicBookgetBook(){ returnthis;//返回Book的类引用 } }复制 2、成员方法 &n

  • 修改win10 默认网卡 --其实就是改网卡接口跃点

    什么式跃点?就是顺序,从1-9999  ,数字越小,优先级越高!手动方式:https://jingyan.baidu.com/article/358570f6bc5cfdce4724fca2.html命令方式: https://blog.csdn.net/pass_kun/article/details/90415810 开始做,坚持做,重复做

  • &#39;webpack&#39;不是内部或外部命令解决办法

    -bash:webpack:未找到命令'webpack',不是内部或外部命令,也不是可运行的程序或批处理文件 如果你的webpack不是全局安装的,那么当你在终端中使用此命令时,需要额外指定其在node_modules中的地址,例如: #webpack非全局安装的情况 node_modules/.bin/webpackapp/main.jspublic/bundle.js复制 或者全局安装webpack npminstallwebpack-g复制   安装后再执行webpack相关命令如果提示: Doyouwanttoinstall'webpack-cli'(yes/no):复制 记得输入no,然后再全局安装webpack-cli即可,不然还会出现其它错误 npminstallwebpack-cli-g复制   .footer-body*{ box-sizing:border-box; } .footer-bodyblockquote{ margin:20px0!important; background-color:#f5f8fc; p

  • 让Scrapy的Spider更通用

    1,引言《Scrapy的架构初探》一文所讲的Spider是整个架构中最定制化的一个部件,Spider负责把网页内容提取出来,而不同数据采集目标的内容结构不一样,几乎需要为每一类网页都做定制。我们有个设想:是否能做一个比较通用的Spider,把定制部分再进一步隔离出去?GooSeeker网络爬虫平台有一个爬虫群模式,从技术实现层面来考察的话,其实就是把爬虫软件做成一个被动接受任务的执行单元,给他什么任务他就做什么任务,也就是说同一个执行单元可以爬多种不同的网站。而分配任务的是GooSeeker会员中心的爬虫罗盘,实现集中管理分布执行。Python开源爬虫项目同样也要尽量实现通用化。主要抓取以下2个重点: 网页内容提取器从外部注入到Spider中,让Spider变通用:参看《Python即时网络爬虫:API说明》,通过API从GooSeeker会员中心获得网页内容提取器,可以充分利用MS谋数台的直观标注快速生成提取器的能力。 抓取目标网址不再存于Spider,而是从外部获得:GooSeeker有个基于大数据平台的网址库系统,还有爬虫罗盘可观察网址的抓取状态,也有用户界面添加删除网址,把

  • 修改部署在 IIS 中项目接口返回的日期时间格式

    问题 Asp.NetCore项目在部署在IIS之后,接口返回的Json的时间日期格式从2019-12-2600:00:00变为了2019/12/260:00:00。 服务器环境 操作系统:WindowsServer2012R2 IIS版本:8.5.9600.16384 解决方案 打开“控制面板”,转到“时钟和区域”选项 打开“区域”选项,在格式选项卡中设置好你需要的日期时间格式 切换到“管理”选项卡,单击“复制设置”,勾选“欢迎屏幕和系统账户”、“新建用户账户”复选框 进入“IIS管理器”,选择“应用程序池” 选择项目,然后单击“高级设置”,编辑“进程模型”中的“标识”选项。将内置账户修改为"LocalService","LocalSystem","NetworkService"这三个的其中一个才会生效,这里我选择的"NetworkService"。 重启网站,或者重启IIS 参考链接 HowtosetDateandtimeformatinIIS7

  • 深度学习(Machine Learning,简称ML)和机器学习(Deep Learning,简称DL)

    2016是人工智能爆发的一年,各种层出不穷的新技术、新概念让人眼花缭乱。很多人都分不清人工智能(ArtificialIntelligence,简称AI)、机器学习(MachineLearning,简称ML)以及深度学习(DeepLearning,简称DL)概念之间的不同。本文为理解机器学习和深度学习提供了不同的视角。

  • swiper设置loop:true,前后点击失效

         let_this=null  

  • Remove Nth Node From End of List

    需要额外添加两个相距为n的节点,遍历一遍之后就可以得到结果。 /** *Definitionforsingly-linkedlist. *publicclassListNode{ *intval; *ListNodenext; *ListNode(intx){ *val=x; *next=null; *} *} */ publicclassSolution{ publicListNoderemoveNthFromEnd(ListNodehead,intn){ ListNodep=head; ListNodene=head; inti=0; while(ne.next!=null){ ne=ne.next; if(i>=n){ p=p.next; } i++; } if(i==0){ returnnull; } elseif(i+1==n){ returnhead.next; } else{ p.next=p.next.next; returnhead; } } }复制  

  • 拓扑排序基础题——排序

    题目 由于公司在2013年的销售业务成绩优秀,公司总经理心情大好,决定给每位员工发奖金。公司决定以每个人本年在公司的贡献为标准来计算他们得到奖金的多少。于是总经理下令召开m方会谈。每位参加会谈的代表提出了自己的意见:“我认为员工a的奖金应该比b高!”。总经理决定要找出一种奖金方案,满足各位代表的意见,且同时使得总奖金数最少。每位员工奖金最少为100元。 输入格式 第一行两个整数n和m,表示员工总数和代表数; 接下来有m行,每行2个整数a和b,表示某个代表认为第a号员工奖金应该比第b号员工高。 输出格式 若无法找到合理方案,则输出“PoorXed”;否则输出一个数表示最少总奖金。 样例数据1 输入 21 12 输出 201 备注 【数据规模】 80%的数据满足:n<=1000,m<=2000; 100%的数据满足:n<=10000,m<=20000。 很简单的一道拓扑排序 如果A的工资要比B高,那就从B向A连一条边 然后从入度为0的点开始统计 然后用fff来记录每个点的工资,当从点i指向点j的时候f[j]=max(f[j],f[i]+1)f[j

  • PREV-55 小计算器

    自动机,模拟 #include<bits/stdc++.h> usingnamespacestd; typedeflonglongintll; //将每次输入的数字转化为十进制操作保存,输出前转化为当前进制输出 //对于自动机形式的题,关键找准运算的顺序和关键节点 //本题以NUM为节点,来对前面存储的字符和数字进行计算 llnum=0; llr=10;//初始为十进制 llget10()//输入相应的数字得到对应十进制值 { lltemp_sum=0,p=1; strings; cin>>s; for(inti=s.length()-1;i>=0;i--){ if(s[i]>'9'){ temp_sum+=p*(s[i]-'A'+10); } elsetemp_sum+=p*(s[i]-'0'); p*=r;//按照对应的进制相加 } returntemp_sum; } voidprint() { strings; llnn=num; if(nn==0) s="0"; while(nn){ charc; if(nn%r<=9) c=(nn%

相关推荐

推荐阅读