OpenMP 原子指令设计与实现

OpenMP 原子指令设计与实现

前言

在本篇文章当中主要与大家分享一下 openmp 当中的原子指令 atomic,分析 #pragma omp atomic 在背后究竟做了什么,编译器是如何处理这条指令的。

为什么需要原子指令

加入现在有两个线程分别执行在 CPU0 和 CPU1,如果这两个线程都要对同一个共享变量进行更新操作,就会产生竞争条件。如果没有保护机制来避免这种竞争,可能会导致结果错误或者程序崩溃。原子指令就是解决这个问题的一种解决方案,它能够保证操作的原子性,即操作不会被打断或者更改。这样就能保证在多线程环境下更新共享变量的正确性。

比如在下面的图当中,两个线程分别在 CPU0 和 CPU1 执行 data++ 语句,如果目前主存当中的 data = 1 ,然后按照图中的顺序去执行,那么主存当中的 data 的最终值等于 2 ,但是这并不是我们想要的结果,因为有两次加法操作我们希望最终在内存当中的 data 的值等于 3 ,那么有什么方法能够保证一个线程在执行 data++ 操作的时候下面的三步操作是原子的嘛(不可以分割):

  • Load data : 从主存当中将 data 加载到 cpu 的缓存。
  • data++ : 执行 data + 1 操作。
  • Store data : 将 data 的值写回主存。

事实上硬件就给我们提供了这种机制,比如 x86 的 lock 指令,在这里我们先不去讨论这一点,我们将在后文当中对此进行仔细的分析。

OpenMP 原子指令

在 openmp 当中 #pragma omp atomic 的表达式格式如下所示:

#pragma omp atomic
表达式;

其中表达式可以是一下几种形式:

x binop = 表达式;
x++;
x--;
++x;
--x;

二元运算符 binop 为++, --, +, -, *, /, &, ^, | , >>, <<或 || ,x 是基本数据类型 int,short,long,float 等数据类型。

我们现在来使用一个例子熟悉一下上面锁谈到的语法:



#include <stdio.h>
#include <omp.h>

int main()
{
  int data = 1;
#pragma omp parallel num_threads(4) shared(data) default(none)
  {
#pragma omp atomic
    data += data * 2;
  }
  printf("data = %d\n", data);
  return 0;
}

上面的程序最终的输出结果如下:

data = 81

上面的 data += data * 2 ,相当于每次操作将 data 的值扩大三倍,因此最终的结果就是 81 。

原子操作和锁的区别

OpenMP 中的 atomic 指令允许执行无锁操作,而不会影响其他线程的并行执行。这是通过在硬件层面上实现原子性完成的。锁则是通过软件来实现的,它阻塞了其他线程对共享资源的访问。

在选择使用 atomic 或锁时,应该考虑操作的复杂性和频率。对于简单的操作和高频率的操作,atomic 更加高效,因为它不会影响其他线程的并行执行。但是,对于复杂的操作或者需要多个操作来完成一个任务,锁可能更加合适。

原子操作只能够进行一些简单的操作,如果操作复杂的是没有原子指令进行操作的,这一点我们在后文当中详细谈到,如果你想要原子性的是一个代码块的只能够使用锁,而使用不了原子指令。

深入剖析原子指令——从汇编角度

加法和减法原子操作

我们现在来仔细分析一下下面的代码的汇编指令,看看编译器在背后为我们做了些什么:


#include <stdio.h>
#include <omp.h>

int main()
{
  int data = 0;
#pragma omp parallel num_threads(4) shared(data) default(none)
  {
#pragma omp atomic
    data += 1;
  }
  printf("data = %d\n", data);
  return 0;
}

首先我们需要了解一点编译器会将并行域的代码编译成一个函数,我们现在看看上面的 parallel 并行域的对应的函数的的汇编程序:

0000000000401193 <main._omp_fn.0>:
  401193:       55                      push   %rbp
  401194:       48 89 e5                mov    %rsp,%rbp
  401197:       48 89 7d f8             mov    %rdi,-0x8(%rbp)
  40119b:       48 8b 45 f8             mov    -0x8(%rbp),%rax
  40119f:       48 8b 00                mov    (%rax),%rax
  4011a2:       f0 83 00 01             lock addl $0x1,(%rax) # 这就是编译出来的原子指令——对应x86平台
  4011a6:       5d                      pop    %rbp
  4011a7:       c3                      retq   
  4011a8:       0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  4011af:       00 

在上面的汇编代码当中最终的一条指令就是 lock addl $0x1,(%rax),这条指令便是编译器在编译 #pragma omp atomic 的时候将 data += 1 转化成硬件的对应的指令。我们可以注意到和普通的加法指令的区别就是这条指令前面有一个 lock ,这是告诉硬件在指令 lock 后面的指令的时候需要保证指令的原子性。

以上就是在 x86 平台下加法操作对应的原子指令。我们现在将上面的 data += 1,改成 data -= 1,在来看一下它对应的汇编程序:

0000000000401193 <main._omp_fn.0>:
  401193:       55                      push   %rbp
  401194:       48 89 e5                mov    %rsp,%rbp
  401197:       48 89 7d f8             mov    %rdi,-0x8(%rbp)
  40119b:       48 8b 45 f8             mov    -0x8(%rbp),%rax
  40119f:       48 8b 00                mov    (%rax),%rax
  4011a2:       f0 83 28 01             lock subl $0x1,(%rax)
  4011a6:       5d                      pop    %rbp
  4011a7:       c3                      retq   
  4011a8:       0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  4011af:       00 

可以看到它和加法指令的主要区别就是 addl 和 subl,其他的程序是一样的。

乘法和除法原子操作

我们现在将下面的程序进行编译:



#include <stdio.h>
#include <omp.h>

int main()
{
  int data = 1;
#pragma omp parallel num_threads(4) shared(data) default(none)
  {
#pragma omp atomic
    data *= 2;
  }
  printf("data = %d\n", data);
  return 0;
}

上面代码的并行域被编译之后的汇编程序如下所示:

0000000000401193 <main._omp_fn.0>:
  401193:       55                      push   %rbp
  401194:       48 89 e5                mov    %rsp,%rbp
  401197:       48 89 7d f8             mov    %rdi,-0x8(%rbp)
  40119b:       48 8b 45 f8             mov    -0x8(%rbp),%rax
  40119f:       48 8b 08                mov    (%rax),%rcx
  4011a2:       8b 01                   mov    (%rcx),%eax
  4011a4:       89 c2                   mov    %eax,%edx
  4011a6:       8d 34 12                lea    (%rdx,%rdx,1),%esi # 这条语句的含义为 data *= 2
  4011a9:       89 d0                   mov    %edx,%eax
  4011ab:       f0 0f b1 31             lock cmpxchg %esi,(%rcx)
  4011af:       89 d6                   mov    %edx,%esi
  4011b1:       89 c2                   mov    %eax,%edx
  4011b3:       39 f0                   cmp    %esi,%eax
  4011b5:       75 ef                   jne    4011a6 <main._omp_fn.0+0x13>
  4011b7:       5d                      pop    %rbp
  4011b8:       c3                      retq   
  4011b9:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)

我们先不仔细去分析上面的汇编程序,我们先来看一下上面程序的行为:

  • 首先加载 data 的值,保存为 temp,这个 temp 的值保存在寄存器当中。
  • 然后将 temp 的值乘以 2 保存在寄存器当中。
  • 最后比较 temp 的值是否等于 data,如果等于那么就将 data 的值变成 temp ,如果不相等(也就是说有其他线程更改了 data 的值,此时不能赋值给 data)回到第一步,这个操作主要是基于指令 cmpxchg

上面的三个步骤当中第三步是一个原子操作对应上面的汇编指令 lock cmpxchg %esi,(%rcx) ,cmpxchg 指令前面加了 lock 主要是保存这条 cmpxchg 指令的原子性。

如果我们将上面的汇编程序使用 C 语言重写的话,那么就是下面的程序那样:



#include <stdio.h>
#include <stdbool.h>
#include <stdatomic.h>

// 这个函数对应上面的汇编程序
void atomic_multiply(int* data)
{
  int oldval = *data;
  int write = oldval * 2;
  // __atomic_compare_exchange_n 这个函数的作用就是
  // 将 data 指向的值和 old 的值进行比较,如果相等就将 write 的值写入 data
  // 指向的内存地址 如果操作成功返回 true 否则返回 false
  while (!__atomic_compare_exchange_n (data, &oldval, write, false,
                                      __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
  {
    oldval = *data;
    write = oldval * 2;
  }
}

int main()
{
  int data = 2;
  atomic_multiply(&data);
  printf("data = %d\n", data);
  return 0;
}

现在我们在来仔细分析一下上面的汇编程序,首先我们需要仔细了解一下 cmpxchg 指令,这个指令在上面的汇编程序当中的作用是比较 eax 寄存器和 rcx 寄存器指向的内存地址的数据,如果相等就将 esi 寄存器的值写入到 rcx 指向的内存地址,如果不想等就将 rcx 寄存器指向的内存的值写入到 eax 寄存器。

通过理解上面的指令,在 cmpxchg 指令之后的就是查看是否 esi 寄存器的值写入到了 rcx 寄存器指向的内存地址,如果是则不执行跳转语句,否则指令回到位置 4011a6 重新执行,这就是一个 while 循环。

我们在来看一下将乘法变成除法之后的汇编指令:

0000000000401193 <main._omp_fn.0>:
  401193:       55                      push   %rbp
  401194:       48 89 e5                mov    %rsp,%rbp
  401197:       48 89 7d f8             mov    %rdi,-0x8(%rbp)
  40119b:       48 8b 45 f8             mov    -0x8(%rbp),%rax
  40119f:       48 8b 08                mov    (%rax),%rcx
  4011a2:       8b 01                   mov    (%rcx),%eax
  4011a4:       89 c2                   mov    %eax,%edx
  4011a6:       89 d0                   mov    %edx,%eax
  4011a8:       c1 e8 1f                shr    $0x1f,%eax
  4011ab:       01 d0                   add    %edx,%eax
  4011ad:       d1 f8                   sar    %eax
  4011af:       89 c6                   mov    %eax,%esi
  4011b1:       89 d0                   mov    %edx,%eax
  4011b3:       f0 0f b1 31             lock cmpxchg %esi,(%rcx)
  4011b7:       89 d6                   mov    %edx,%esi
  4011b9:       89 c2                   mov    %eax,%edx
  4011bb:       39 f0                   cmp    %esi,%eax
  4011bd:       75 e7                   jne    4011a6 <main._omp_fn.0+0x13>
  4011bf:       5d                      pop    %rbp
  4011c0:       c3                      retq   
  4011c1:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  4011c8:       00 00 00 
  4011cb:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

从上面的汇编代码当中的 cmpxchg 和 jne 指令可以看出除法操作使用的还是比较并交换指令(CAS) cmpxchg,并且也是使用 while 循环。

其实复杂的表达式都是使用这个方式实现的:while 循环 + cmpxchg 指令,我们就不一一的将其他的使用方式也拿出来一一解释了。简单的表达式可以直接使用 lock + 具体的指令实现。

总结

在本篇文章当中主要是深入剖析了 OpenMP 当中各种原子指令的实现原理以及分析了他们对应的汇编程序,OpenMP 在处理 #pragma omp atomic 的时候如果能够使用原子指令完成需求那就直接使用原子指令,否则的话就使用 CAS cmpxchg 指令和 while 循环完成对应的需求。


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

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

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

相关文章

  • 如何开启 ABAP Update function module 和系统程序的调试功能

    有朋友咨询,在这些updatefunctionmodule里设置了断点,但是运行时,断点并没有停下来,这是为什么?这位朋友有这样的疑问:是有什么特殊设置?还是SAP故意不让我们debug有些程序?比如里我无论怎么设置breakpoint,怎么都跳不进去。其实不是这样的,ABAP里的代码,无论是updatefunctionmodule,还是系统程序(systemprogram),都是可以调试的,设置如下。所谓updatefunctionmodule,就是SE37里UpdateModule前面的勾被选上的函数:在updatefunctionmodule执行之前,启动调试器,选择菜单:Settings->ChangeDebuggerProfile/Settings: 在弹出的对话框里,一定要记得把SystemDebugging和UpdateDebugging前面的✓打上:最后一定要记住,点击保存按钮,这样才能将当前的修改保存。

  • 「吃鸡」之父自立门户!研发新沙盒游戏连接元宇宙

    【新智元导读】失控玩家要来临?近日,《绝地求生》之父BrendanGreene宣布离职,并成立了新工作室。他和团队未来将致力于新作Prologue的开发,基于神经网络打造一个大规模开放世界。昨晚,王者荣耀崩了...这一话题迅速冲上热搜,引起许多网友的反响。有网友表示,一周就3小时,游戏还崩了。王者荣耀官微称,「针对因服务器影响而丢失对局的胜利方,将陆续补发对应『排位星积分』和『巅峰赛积分』」。同样,深受大家喜爱的另一款游戏——《绝地求生》之父BrendanGreene近日宣布了离职。他表示,将成立自己的新工作室,继续开发新沙盒游戏。还吃鸡吗?PUBG之父转阵地Brendan上周宣布离开韩国Krafton公司,并发表视频宣告了这一消息。他在视频中感谢所有游戏开发者和玩家,共同将这一简单的游戏模式扩展成一系列令人惊叹的游戏种类。BrendanGreene表示,他非常热爱沙盒式开放世界游戏,因为这能够给玩家极大的自由。为了创建一个具有更大世界、真实体验的沙盒游戏,他和他的团队成立了一个新的工作室,名为PlayerUnknownProductions。他于6年前发明了「大逃杀」这一游戏类型,最

  • go实现堆排序、快速排序、桶排序算法

    一.堆排序  堆排序是利用堆这种数据结构而设计的一种排序算法。以大堆为例利用堆顶记录的是最大关键字这一特性,每一轮取堆顶元素放入有序区,就类似选择排序每一轮选择一个最大值放入有序区,可以把堆排序看成是选择排序的改进。它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。首先简单了解下堆结构。堆堆是一棵完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:对堆中的结点按层进行编号,将这种逻辑结构映射到数组中: 由于它是一颗完全二叉树,所以满足序号 leftchild=parent*2+1;复制rightchild=parent*2+2;复制这样的特性,利用这一特性,每次将parent与child进行比较然后向下调整元素的位置。实现堆排序将初始待排序关键字序列(R0,R1,R2....Rn)构建成大顶堆,此堆为初始的无序区;初始堆满足大顶堆性质,但是元素无序。依次将将堆顶元素R[0]与最后一个元素R[n]交换,此时得到新的无序区(R0,R1,R2,......Rn-1)和新的有序区(Rn);交

  • 直播系统源码搭建完成后需要注意哪些方面?

    直播系统源码的模块划分视频服务器端:视频传输和播放用的流媒体服务器,通常是用C或者C++语言开发实现,主要实现一对多的视频流发布功能。内容分发系统:很多人都知道,涉及到大规模内容分发都需要用到CDN技术,并且市场上出现了很多专门提供CDN服务的上市公司,他们通过为用户提供内容的大范围分发服务来盈利。直播系统源码的分发主要有以下特点:1、flv居多,ts较少,原因主要是ts标准太过于复杂。Flv的标准开放文档是11页,ts的有174页。对于一般的直播,flv基本能满足需求,因此ts应用就较少。当然了,我们也可以借助于FFmpeg,但是它会将流媒体方面你想得到的和想不到的都封装了,不够精准。2、rtmp和hls并存。一般来讲,rtmp用在PC端上,使用flash播放;hls用作手机和平板上。3、实时流一般使用rtmp。rtmp能做到1到3秒的延迟,是直播里除了rtsp外延迟最低的协议。PC上支持直接播放,移动端可以用FFmpeg解码播放。直播系统源码搭建完成后需要注意的方面:关键页面的响应时间如果用户访问的某个页面的跳出率过高,那么你就需要对该页面进行验证了。尤其是在结账、添加物品到购物车

  • 事件相机角点检测,从原理到demo

    上次提到了事件相机的数据集和运行平台,这次我们运行第一个实例,从简单的入手:角点检测。一、事件相机Harris角点检测提到角点检测,非常出名的便是Harris角点,在传统图像领域使用非常多。但为了在事件相机的数据形式中使用,我们首先需要了解Harris角点检测的基本原理。1、Harris角点检测原理首先定义什么算是一个角点。Harris角点认为,在角点附近的区域中,无论沿任何方向移动图像,都会造成图像灰度的明显变化,那么这个点就是一个角点。注意到这里的描述提到了“任意”方向,也就是说,当单独有一条直线时,若沿着直线延伸方向移动,是不会造成图像灰度发生变化的,所以不是角点。接下来,为了从数学角度描述“移动导致图像灰度发生变化”,我们用目标函数E进行表示,表示图像I(·)经过(u,v)移动后在窗口W中的像素变化情况。变化越明显,E越大。我们认为在角点附近的权重应该大一些,则为E增加以(x,y)为中心的高斯权重。当完成数学定义后,我们进行数学上的变换。首先通过Taylor展开,将平方项中的第一个表示为当前像素和像素所在处梯度沿x和y方向强度Ix,Iy,以及偏移(u,v)的形式,则有:从Har

  • 一枚 架构师 也不会用的Lombok注解,相见恨晚

    原创:不羡鸳鸯不羡仙,一行代码调半天。小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。我见过很多反对Lombok的同学,背地里又偷偷的把插件添加了进去,这是真香原理在搞鬼。嘴上说不要,身体很诚实。反对的人,应该是没见过一些业务代码的冗长繁杂,还沉浸在自己病态的完美主义中。要面对又脏又乱的从业环境,面对现实。Lombok可以消除Java的冗长,减少代码的长度,让关注点转移到该专注的地方。SpringBoot把Lombok放到了它的依赖中,Java14甚至也借鉴了这种思想,推出了record语法,就是类似于下面这种:recordPoint(intx,inty){} 复制本篇文章,不打算讨论什么类似于@Data注解之类的。我们讨论一个比较偏门的,但是又让你感觉相见恨晚的一个注解:RequiredArgsConstructor。爆炸的属性注入Spring提供了两种注入模式,这也是非常初级的程序员经常被问到的三种DI写法。一种是属性注入(Filedinjection),一种是通过setter方法,一种是构造器注入。霍霍,我撒谎了,经常被问的是byName和byType。不过

  • 设置图例不能点击

    option8:{ color:["#00CCFF","#FE713A"], legend:{ data:["最高分","最低分"], icon:"circle", selectedMode:false///设置图例不能点击 }, grid:{ left:"10%", right:"2%", bottom:"20%", top:"20%" }, xAxis:[ { type:"category", name:"每次考试时间", data:[], axisTick:{ alignWithLabel:true }, axisLabel:{ textStyle:{ color:"#333", fontSize:12 } //rotate:38 } } ], yAxis:[ { type:"value", name:"分数&qu

  • 004内部仓储物流的“降维打击”之 二

    接着上次分享的内部仓储物流系统的降维打击,这里再整理几个仓储物流自动化方面的技术更替和发展。让“降维打击”继续表演。托盘密集存储系统原始方案0度:随着近代商业的繁荣发展,越来越多的仓储中心希望能在有限的厂房空间里放置最大量的货物。最初没有仓库搬运机械的时期,最简单的多放货物的办法就是托盘货物分片区放置在地面上,或者用叉车将货物挑到几层货架里。二、1维打击方案:由于叉车需要一定的工作空间,叉取一排货架时需要的货架通道一般至少需要2.5米的距离。在一个仓库里,除掉货架占的面积,有很大一部分占地空间都被叉车工作空间占用了。因此采用可移动的货架可以减少叉车的占用空间,即每排货架平时都靠在一起,需要取哪排货架上的货物,移动相对的货架排,留出叉车可以工作的通道。打击点:同样的占地面积,用移动货架只需要留出来1个或者2个叉车通道即可,节省出来的原来叉车占地面积都可以安装货架进行货物存放,有效利用空间,提升货物存量。三、2维打击方案:移动货架可以省掉大量的叉车通道,但在实际操作中比较麻烦,每次存取货物时需要人工开启移动机构,如果当前要移开的货架和上次移开的货架距离较远,那需要对中间的货架进行顺序移动才

  • centos6.4升级Python过程总

    1、查看Python环境: python -V复制2、下载Python包并解压cd /usr/local/src/   #不一定在这个目录下,只是我习惯在此目录 wget wget http://python.org/ftp/python/2.7.3/Python-2.7.3.tar.bz2 tar xf  Python-2.7.3.tar.bz2复制3、编译安装 cd Python-2.7.3 ./configure make && make install复制4、创建软连接mv /usr/bin/python /usr/bin/python2.6.6  #根据自己的版本修改 ln -s /usr/local/bin/python2.7 /usr/bin/python复制5、检查是否成功 python -V复制当出现Python2.7.3即为安装成功##################################################因为Python版本升级后,yum会出现不可用现象,所以解决这一现象的方法就是: 1、修改yum配置文件:vim /usr

  • 常用的 Git 命令,给你准备好了!

    分支操作:gitbranch创建分支gitbranch-b创建并切换到新建的分支上gitcheckout切换分支gitbranch查看分支列表gitbranch-v查看所有分支的最后一次操作gitbranch-vv查看当前分支gitbrabch-b分支名origin/分支名创建远程分支到本地gitbranch--merged查看别的分支和当前分支合并过的分支gitbranch--no-merged查看未与当前分支合并的分支gitbranch-d分支名删除本地分支gitbranch-D分支名强行删除分支gitbranchorigin:分支名删除远处仓库分支gitmerge分支名合并分支到当前分支上暂存操作:gitstash暂存当前修改gitstashapply恢复最近的一次暂存gitstashpop恢复暂存并删除暂存记录gitstashlist查看暂存列表gitstashdrop暂存名(例:stash@{0})移除某次暂存gitstashclear清除暂存回退操作:gitreset--hardHEAD^回退到上一个版本gitreset--hardahdhs1(commit_id)回退到某

  • Keras 作者François新作:通往真正的智能需要测量「智慧」,而非测量某个具体能力

    测量智慧编译|杨晓凡 编辑|唐里 著名深度学习研究员、谷歌大脑研究员、Keras库作者(以及Twitter活跃分子)FrançoisChollet近期在arXiv上公开了一篇论文《TheMeasureofIntelligence》(arxiv.org/abs/1911.01547)。正如标题,这篇论文讨论的是人类应该如何理解以及正确地测量生命体/智能体的智慧。FrançoisChollet对机器学习领域里「大肆炒作模型在单个任务中的表现」的惯例非常不满,他认为这并不能体现「智慧」。比如CNN家族在ImageNet图像分类中超越人类、AlphaGo在围棋中击败人类、OpenAIFive在DOTA2中击败人类、AlphaStar在星际2中击败人类,诸如此类的学术研究进展,即便确实是在非常复杂的任务中取得了比人类更好的表现,我们也无法认可这些模型就拥有了「智慧」。相比之下,乌鸦、海豚之类的动物更被人类认为是「有智慧」的。所以,AI系统是否拥有「智慧」的标准可能是什么样的,我们又应该用什么样的方法测量AI系统,这就是FrançoisChollet在这篇论文中着重讨论的。核心观点AI研究员Emi

  • MyBatis框架之第一篇

    版权声明:本文为博主原创文章,遵循CC4.0BY-SA版权协议,转载请附上原文出处链接和本声明。本文链接:https://blog.csdn.net/zhao1299002788/article/details/102136068MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apachesoftwarefoundation迁移到了googlecode,并且改名为MyBatis。2013年11月迁移到Github。 ●【GitHub】 GitHub就是一个互联网上的超大SVN库,里面维护着许多开源项目的代码。任何人都可以把自己好多项目代码放上去共享,同时接受其他人的共同开发。 2.2.什么是MyBatis MyBatis是使用java语言编写的一个优秀的持久层框架,是对JDBC操作数据库的过程进行了全新的封装。解决了JDBC中的问题。 Mybatis框架隐藏了jdbc繁杂的业务无关代码: ·手动注册驱动、创建connection、statement ·手动设置参数以及参数的顺序 ·手动遍历结果集 使开发

  • 腾讯云数据开发治理平台WeData实例批量重跑【Beta版本】api接口

    1.接口描述接口请求域名:wedata.tencentcloudapi.com。 [注意:该Beta版本只满足广州区部分白名单客户使用] 实例批量重跑 默认接口请求频率限制:20次/秒。 APIExplorer提供了在线调用、签名验证、SDK代码生成和快速检索接口等能力。您可查看每次调用的请求内容和返回结果以及自动生成SDK调用示例。 2.输入参数以下请求参数列表仅列出了接口请求参数和部分公共参数,完整公共参数列表见公共请求参数。 参数名称 必选 类型 描述 Action 是 String 公共参数,本接口取值:RerunInstances。 Version 是 String 公共参数,本接口取值:2021-08-20。 Region 是 String 公共参数,详见产品支持的地域列表。 ProjectId 是 String 项目Id Instances.N 是 ArrayofInstanceInfo 实例嵌套集合 CheckFather 是 Boolean 检查父任务类型,true:检查父任务;false:不检查父任务 Rer

  • Spring Beans线程安全吗?

    不. Spring有不同的bean 范围(例如Prototype,Singleton等),但是所有这些范围强制都在创建bean时 进行.例如,每次"注入"一个"原型"范围的bean都会被创建,而一个"单个"范围的bean将被创建一次并在应用程序上下文中共享.还有其他范围,但是它们只是定义了何时创建新实例的时间范围(例如"范围"). 以上内容与线程安全无关,因为如果多个线程可以访问一个bean(无论范围如何),那么它仅取决于该bean的 design 是或不是"线程安全"的. 我之所以说"很少,如果有的话"是因为它可能取决于您要解决的问题.例如,如果您担心两个或更多个HTTP请求是否可能对同一个bean造成问题,则有一个"请求"范围,它将为每个HTTP请求创建一个新的bean实例,因此您可以将"特定"bean视为"在多个HTTP请求的上下文中是"安全的".但是 仍然不是真正的线程安全线程,因为如果多个线程在同一 HTTP请求中使用此bean,它将返回到bean设计( your bean支持类的设计. 如何使/

  • 使用protobuf编译onnx.proto过程中的一些问题总结

    使用gitclone下载protobuf的源代码,然后gitcheckout到branch2.7.0:编译protobuf,先在代码顶层目录执行./configure,然后执行make,成功后执行sudoldconfig,重新加载动态库。经过试验发现,使用protoc编译onnx.proto,需要特别注意版本问题,使用3.5.1的版本,编译都会失败,而且中途会有提示gcc版本过低的错误信息,在4.9一下都不支持之类的信息,这个时候去升级gcc版本到了5.5,回来发现问题依然存在。最后使用的protoc版本是2.7.0,这是通过冲git上将master源代码下载下来,然后使用gitchekoutbranch的方式,进行源代码编译安装完成的。如果系统中原来通过pip或者apt-get之类的工具安装过protobuf,那么系统会在/usr/bin/下安装protoc,在/usr/lib/下安装有libprotbuf*等so文件,这个时候可以直接将这些文件全部rm-fr。因为linux搜索二进制文件和so文件的顺序是先搜索/usr/bin/,/usr/lib/,然后再搜索/usr/local/

  • 目录和头文件划分

    root_inc  common_inc  mod_a_inc  mod_b_inc   dir_mod_a  mod_a_inc  mod_a_submod1    a1_func.c    a1_func.h  mod_a_submod2    a2_func.c    a2_func2.h   dir_mod_b  mod_b_inc  mod_b_submod1    b1_func.c    b1_func.h  mod_b_submod2    b2_func.c    b2_func.h  

  • elementUI的el-table和el-checkbox组合形成表格多选框

    看下效果 我这里实则用到了el-dialog、el-table和el-checkbox,主要的是el-table和el-checkbox。 <el-dialog title="简号表" :visible.sync="showJhTable" width="100%" class="jhbdia" top="1vh" @closed="clearJh" > <el-checkbox-groupv-model="selectJh":min="0":max="1"> <el-table :data="jhData" :header-cell-style="{'text-align':'center'}" :show-header="false" > <el-table-column width="75" align="left" v-for="numin20" :key="num" > <templateslot-scope="scope"v-if="scope.row.length>=num"> <el-check

  • 每日分享

    一、JQuery中的AJAX方法   $.post、$.get是一些简单的方法,如果要处理复杂的逻辑,还是需要用到jQuery.ajax()   一、$.ajax的一般格式 $.ajax({    type:'POST',    url:url,   data:data,   success:success,   dataType:dataType });   二、$.ajax的参数描述 参数描述 url必需。规定把请求发送到哪个URL。 data可选。映射或字符串值。规定连同请求发送到服务器的数据。 success(data,textStatus,jqXHR)可选。请求成功时执行的回调函数。 dataType  可选。规定预期的服务器响应的数据类型。 默认执行智能判断(xml、json、script或html)。   三、$.ajax需要注意的一些地方:  1.data主要方式有三种,html拼接的,jso

  • java基础-04泛型

    介绍 泛型就是数据类型的参数化表示,泛型的本质是参数化类型,常用E代表任何数据类型,在实际使用的时候把实际的数据类型传递给E。 泛型的好处是设计通用的功能,多个数据类型可以共用。 泛型类型E只能代表Object类型,不能代表int,double等基本类型,要使用Integer,Double代替这些基本类型。 因为E的上限类型默认是Object,因此只能使用Object类型或Object的子类。 泛型分类 接口泛型 //集合泛型接口,在接口名后添加<E> publicinterfaceCollection<E>{ //接口中的泛型方法,直接使用接口中的泛型参数当做数据类型 booleanadd(Ee); } 复制 类泛型 //列表泛型类,在类名后面添加<E> publicclassArrayList<E>extendsAbstractList<E>implementsList<E>{ //泛型类中的泛型方法 publicbooleanadd(Ee){ returntrue; } } 复制 方法泛型 //泛型方法,

  • 访问量破千辣!

    20190313:

  • 前端ps切图

        Photoshop界面设置 一.新建设置 Ctrl+n     二.移动工具设置 自动选择去掉,然后选择图层     三.视图设置 添加只能参考线   添加标尺      保留4大视图选项         设置单位为像素         新建一个工作区            Photoshop基本操作    一.简单工具操作 选择工具 在选中模式下,按住Shift为增加选区,按住Alt为减少选区。 按住shift键可以将形状变成正方,正圆 按住Alt键可以找到圆心,以圆心为基准     套锁工具 磁性套索工具会根据颜色进行选择         二.图层的原理及其操作 三.参考线及辅助  

相关推荐

推荐阅读