在本篇文章当中主要与大家分享一下 openmp 当中的原子指令 atomic,分析 #pragma omp atomic
在背后究竟做了什么,编译器是如何处理这条指令的。
加入现在有两个线程分别执行在 CPU0 和 CPU1,如果这两个线程都要对同一个共享变量进行更新操作,就会产生竞争条件。如果没有保护机制来避免这种竞争,可能会导致结果错误或者程序崩溃。原子指令就是解决这个问题的一种解决方案,它能够保证操作的原子性,即操作不会被打断或者更改。这样就能保证在多线程环境下更新共享变量的正确性。
比如在下面的图当中,两个线程分别在 CPU0 和 CPU1 执行 data++ 语句,如果目前主存当中的 data = 1 ,然后按照图中的顺序去执行,那么主存当中的 data 的最终值等于 2 ,但是这并不是我们想要的结果,因为有两次加法操作我们希望最终在内存当中的 data 的值等于 3 ,那么有什么方法能够保证一个线程在执行 data++ 操作的时候下面的三步操作是原子的嘛(不可以分割):
事实上硬件就给我们提供了这种机制,比如 x86 的 lock 指令,在这里我们先不去讨论这一点,我们将在后文当中对此进行仔细的分析。
在 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)
我们先不仔细去分析上面的汇编程序,我们先来看一下上面程序的行为:
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、计算机系统基础、算法与数据结构)知识。
有朋友咨询,在这些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年前发明了「大逃杀」这一游戏类型,最
一.堆排序 堆排序是利用堆这种数据结构而设计的一种排序算法。以大堆为例利用堆顶记录的是最大关键字这一特性,每一轮取堆顶元素放入有序区,就类似选择排序每一轮选择一个最大值放入有序区,可以把堆排序看成是选择排序的改进。它的最坏,最好,平均时间复杂度均为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解码播放。直播系统源码搭建完成后需要注意的方面:关键页面的响应时间如果用户访问的某个页面的跳出率过高,那么你就需要对该页面进行验证了。尤其是在结账、添加物品到购物车
上次提到了事件相机的数据集和运行平台,这次我们运行第一个实例,从简单的入手:角点检测。一、事件相机Harris角点检测提到角点检测,非常出名的便是Harris角点,在传统图像领域使用非常多。但为了在事件相机的数据形式中使用,我们首先需要了解Harris角点检测的基本原理。1、Harris角点检测原理首先定义什么算是一个角点。Harris角点认为,在角点附近的区域中,无论沿任何方向移动图像,都会造成图像灰度的明显变化,那么这个点就是一个角点。注意到这里的描述提到了“任意”方向,也就是说,当单独有一条直线时,若沿着直线延伸方向移动,是不会造成图像灰度发生变化的,所以不是角点。接下来,为了从数学角度描述“移动导致图像灰度发生变化”,我们用目标函数E进行表示,表示图像I(·)经过(u,v)移动后在窗口W中的像素变化情况。变化越明显,E越大。我们认为在角点附近的权重应该大一些,则为E增加以(x,y)为中心的高斯权重。当完成数学定义后,我们进行数学上的变换。首先通过Taylor展开,将平方项中的第一个表示为当前像素和像素所在处梯度沿x和y方向强度Ix,Iy,以及偏移(u,v)的形式,则有:从Har
原创:不羡鸳鸯不羡仙,一行代码调半天。小姐姐味道(微信公众号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
接着上次分享的内部仓储物流系统的降维打击,这里再整理几个仓储物流自动化方面的技术更替和发展。让“降维打击”继续表演。托盘密集存储系统原始方案0度:随着近代商业的繁荣发展,越来越多的仓储中心希望能在有限的厂房空间里放置最大量的货物。最初没有仓库搬运机械的时期,最简单的多放货物的办法就是托盘货物分片区放置在地面上,或者用叉车将货物挑到几层货架里。二、1维打击方案:由于叉车需要一定的工作空间,叉取一排货架时需要的货架通道一般至少需要2.5米的距离。在一个仓库里,除掉货架占的面积,有很大一部分占地空间都被叉车工作空间占用了。因此采用可移动的货架可以减少叉车的占用空间,即每排货架平时都靠在一起,需要取哪排货架上的货物,移动相对的货架排,留出叉车可以工作的通道。打击点:同样的占地面积,用移动货架只需要留出来1个或者2个叉车通道即可,节省出来的原来叉车占地面积都可以安装货架进行货物存放,有效利用空间,提升货物存量。三、2维打击方案:移动货架可以省掉大量的叉车通道,但在实际操作中比较麻烦,每次存取货物时需要人工开启移动机构,如果当前要移开的货架和上次移开的货架距离较远,那需要对中间的货架进行顺序移动才
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
分支操作: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库作者(以及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
版权声明:本文为博主原创文章,遵循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 ·手动设置参数以及参数的顺序 ·手动遍历结果集 使开发
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有不同的bean 范围(例如Prototype,Singleton等),但是所有这些范围强制都在创建bean时 进行.例如,每次"注入"一个"原型"范围的bean都会被创建,而一个"单个"范围的bean将被创建一次并在应用程序上下文中共享.还有其他范围,但是它们只是定义了何时创建新实例的时间范围(例如"范围"). 以上内容与线程安全无关,因为如果多个线程可以访问一个bean(无论范围如何),那么它仅取决于该bean的 design 是或不是"线程安全"的. 我之所以说"很少,如果有的话"是因为它可能取决于您要解决的问题.例如,如果您担心两个或更多个HTTP请求是否可能对同一个bean造成问题,则有一个"请求"范围,它将为每个HTTP请求创建一个新的bean实例,因此您可以将"特定"bean视为"在多个HTTP请求的上下文中是"安全的".但是 仍然不是真正的线程安全线程,因为如果多个线程在同一 HTTP请求中使用此bean,它将返回到bean设计( your bean支持类的设计. 如何使/
使用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/
看下效果 我这里实则用到了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
介绍 泛型就是数据类型的参数化表示,泛型的本质是参数化类型,常用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; } } 复制 方法泛型 //泛型方法,