-
很荣幸地能参与到华为鲲鹏众智计划项目中,本次参与的项目主要是将远程客户端调用Kunpeng服务器(openEuler)中的TrustZone特性,满足了虚拟化或VM场景下更便利的使用TrustZone特性的安全需求。在本次项目中,我从开发到测试再到部署以及文档编写等环节中受益良多,感受到了本科阶段体验过的完整开发流程,与华为的合作也是让我体会到了一个优秀公司对于项目开发的严谨与求真。 在项目开始阶段通过与华为项目团队的沟通,华为方面详细的为我们介绍了这个项目的需求、目的以及验收标准,并给出了详细且完善的文档,为之后的开发做了充足的准备。在项目初期,主要由导师牵头为我们分配任务,每个人都有各自负责的模块,我负责的模块是服务器端的内部数据透传,将数据从一个进程传递到另一个进程,并对其中的进程与线程作排队处理。 通过该项目,让我对华为的Openeuler系统操作有了更深刻的认识,熟悉了在无图形界面的终端上进行操作,并且在对服务器操作的过程中,了解到了如何去维护服务器、服务器如何进行内网透传等操作,对企业级的开发有了更深刻的认识。在Openeuler上进行开发,没有windows开发的编译器,对于命令行操作的要求更为严格,通过不断地学习,我也对linux开发更加熟练并且产生了兴趣。在服务器上代码的调试与测试需要使用另一套软件,对其调试的过程中,更加加深了我对于内存、指针等本科未深入学习方向的认识。 在开发的过程中,我也掌握了如何使用开源代码针对所需功能进行编写与开发,在遇到困难时可以尝试多种不同的方法进行调试,如分配进程到内核,可以使用产生与时间有关的随机数进行随机分配,以保证得出的进程编号不会重复。在编译过程中,编译时需要自己编写makefile文件,该过程让我了解到了在windows上使用编译器时未曾学习到的,编译的基本逻辑,受益良多。 在测试过程中,针对每个接口进行单独测试、并发测试、容器测试等内容,让我了解到除了实现功能,实现的功能的效率也至关重要,对于企业级的项目开发也有了全新的认识。 在文档编写阶段,我了解到了如何去描述一个项目,并且清楚、简洁且直观地表述出来,如何将一件事情清晰的表达出来,也是对我个人能力的提升。 在本次与华为项目的合作中,我清晰地认识到了团队合作的重要性,开拓了视野,锻炼了我的实战能力,最后感谢华为提供了鲲鹏众智平台提供了项目实践的平台。希望在今后能与华为公司进一步合作,做出更好的成绩。武汉大学 - 余发江研究团队 - 杨一夫,指导老师:余发江
-
一、什么 JavaScript 是单线程?JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript 不能有多个线程呢 ?这样能提高效率啊。JavaScript 的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript 就是单线程,这已经成了这门语言的核心特征,将来也不会改变。二、JavaScript是单线程,怎样执行异步的代码?单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。消息队列:消息队列是一个先进先出的队列,它里面存放着各种消息。事件循环:事件循环是指主线程重复从消息队列中取消息、执行的过程。实际上,主线程只会做一件事情,就是从消息队列里面取消息、执行消息,再取消息、再执行。当消息队列为空时,就会等待直到消息队列变成非空。而且主线程只有在将当前的消息执行完成后,才会去取下一个消息。这种机制就叫做事件循环机制,取一个消息并执行的过程叫做一次循环。三、事件循环机制上图大致描述就是:主线程运行时会产生执行栈,栈中的代码调用某些 api 时,它们会在事件队列中添加各种事件(当满足触发条件后,如 setTimeout 执行完毕)而栈中的代码执行完毕,就会读取事件队列中的事件,去执行那些回调如此循环注意,总是要等待栈中的代码执行完毕后才会去读取事件队列中的事件
-
在跟余老师学习的过程中,有机会接触到华为TrustZone机密计算资源池化项目,从开始的一头雾水到后来熟练制作,每一遍的相似操作中总会有新的发现,整体做下来最大的收获就是对linux系统底层的运作又有了更加深刻的理解,相比技术性的提高更重要的是相关的专业素养得到增加,以及知识面得到拓广,并且和华为的合作使我受益匪浅。 在该项目中我们实现了远程客户端调用Kunpeng服务器(openEuler)中的TrustZone特性,满足了虚拟化或VM场景下更便利的使用TrustZone特性的安全需求。在其中开发出了进程池。其中我的主要学习了是Kunpeng服务器上工作环境的搭建与共享内存方面的知识。 共享内存指在多处理器的计算机系统中,可以被不同中央处理器访问的大容量内存。由于多个CPU需要快速访问存储器,这样就要对存储器进行缓存。任何一个缓存的数据被更新后,由于其他处理器也可能要存取,共享内存就需要立即更新,否则不同的处理器可能用到不同的数据。共享内存是 Unix下的多进程之间的通信方法 ,这种方法通常用于一个程序的多进程间通信,实际上多个程序间也可以通过共享内存来传递信息。在调用远程服务器中的TrustZone时,通过共享内存使物理上隔离的机器之间能便捷快速的调用命令,传递信息。 本项目中的共享内存主要思路如下,与大家共同学习: 1 部署在容器或VM中的客户CA调用PARSECGP Client 连接TEE接口TEEC_InitializeContext,PARSEC GP Client将调用通过PARSEC Interface发到服务器端;; 2 服务侧的PARSEC Provider收到调用后,识别为GP初始连接接口,转换为本地GP client调用,通过本地的lso获得上下文context,之后通过返回通道将接口返回到客 户CA应用; 3 客户CA在已创建的context连接上创建会话,指定TA,获得session。这个过程类似于获得context的过程,涉及组件都是透传为主,不再赘述;注意以上过程中PARSEC GP Provider需要维护上下文以及会话关系。 4 根据客户CCA自己的设计,如果CCA是计划使用内存来传递参数的(此例是采用临时申请的内存),则需要使用TEECC_AllocateSharedMemory接口申请临时内存。这个申请需要在已存的Context下进行,接口调用时需要在TEEC_SharedMemory结构中填入大小,以及类型(输入还是输出)。 5 TEEC_SharedMemory调用在PARSECGP Client终结,并不会直接透传到服务器,PARSEC GP Client根据CA输入的大小和类型,创建buffer,将指针返回CA应用; 6 CA发起操作调用,在操作结构中,会指定参数的传递的方式(paramTypes),以及参数信息(params);PARSEC GP Client需要进行识别,选择不同的分支处理: 1)如果paramType==TEEC_VALUE_*类型,表示此次调用的参数采用直接传递的方式,PARSEC GP Client通过PARSEC直接透传调用,将调用透传到服务器PARSEC GP Provider,触发本地调用,并从服务器本地获得处理结果; 2)如果paramType== TEEC_MEMREF_*表示此调用的参数是通过内存共享传递的: a. PARSECGP Client库会将GP操作发往provider,在TEECC_InvokeCommand中关键参数除了commandID,增加了参数buffer,将数据一起通过interface传至GP provider、GP Proxy、GP Worker。GP Worker根据不同的参数类型进行allocate。即现在Invoke命令中就可以直接传buffer,而不是走两个来回去传buffer。 b. GP Worker获取到了数据以及指令后,调用本地的标准的TEEC_InvokeCommand,并接收返回。 7 PARSEC GP Client库需要继续维护来自CCA的内存释放接口。注意释放接口是针对内存buffer精确的操作,即可能会存在多个buffer。收到调用后,PARSEC GP Client即刻将本地指定的buffer清零,之后再通过扩展的远程数据清除操作(0x0108),将buffer指针送到GP Proxy,GP Proxy传到GP Worker。GP Worker收到调用后,找到对应的内存清零,接口调用本地标准的TEEC_ReleaseSharedMemory,清除TEE侧对应的数据(注意此时需要使用真实的buffer指标)。CCA和GP Worker分别释放。 本次的项目进展顺利,与华为的合作非常高效,在遇到问题的时候,团队内及时配合,与华为的技术人员及时沟通,都是顺利推进的关键因素。技术性相关知识的学习,以及如何理解项目要求,学习并运用相关知识以完成项目,与同学老师的合作,都使我受益良多。武汉大学 - 余发江研究团队 - 毛煜灵,指导老师:余发江
-
Runnable VS CallableCallable仅在 Java 1.5 中引入,目的就是为了来处理Runnable不支持的用例。Callable 接口可以返回结果或抛出检查异常Runnable 接口不会返回结果或抛出检查异常,如果任务不需要返回结果或抛出异常推荐使用 Runnable接口,这样代码看起来会更加简洁工具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转换。(Executors.callable(Runnable task)或 Executors.callable(Runnable task,Object resule))shutdown() VS shutdownNow()shutdown() :关闭线程池,线程池的状态变为 SHUTDOWN。线程池不再接受新任务了,但是队列里的任务得执行完毕。shutdownNow() :关闭线程池,线程的状态变为 STOP。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。 shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终isTerminated() VS isShutdown()isShutDown 当调用 shutdown() 方法后返回为 true。isTerminated 当调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true
-
一、一条查询SQL是如何执行的?1.客服端/服务器通信协议2.查询缓存,若缓存中有数据直接返回,若没有执行第三步3.解析器进行语法解析,解析树预处理给到新解析树,4.查询优化器5执行计划,查询计划引擎6.返回结果,并缓存结果。API接口查询到存储引擎1.通信协议 首先,MySQL 必须要运行一个服务,监听默认的3306端口MYSQL支持TCP/IP 协议,编程语言的连接模块都是用 TCP 协议连接到 MySQL 服务器的。MYSQL还支持Unix Socket。比如我们在 Linux 服务器,不用通过网络协议,也可以连接到 MySQL 的服务器。2.通信方式分为三种A(发送器)--->B(接收器) 单工,数据单向传输A(发送器,接收器)<--->B(发送器,接收器) 半双工,数据双向传输,但是不能同时传输A(发送器,接收器)<===>B(发送器,接收器) 双工,数据双向传输,同时传输MySQL 使用半双工的通信方式。 半双工意味着要么是客户端向服务端发送数据,要么是服务端向客户端发送数据,这两个动作不能 同时发生。 所以客户端发送 SQL 语句给服务端的时候,(在一次连接里面)数据是不能分成小块发送的,不管 你的 SQL 语句有多大,都是一次性发送。 如果发送给服务器的数据包过大,我们必须要调整 MySQL 服务器配置 max_allowed_packet 参数的值 (默认是 4M)另一方面,对于服务端来说,也是一次性发送所有的数据,不能因为你已经取到了想要的数据就中 断操作。 所以,我们一定要在程序里面避免不带 limit 的这种操作。连接方式8小时不活动的连接,MySQL 服务器会断开。MySQL 默认的最大连接数是 151 个(5.7 版本)Sleep 线程正在等待客户端,以向它发送一个新语句 Query 线程正在执行查询或往客户端发送数据 Locked 该查询被其它查询锁定 Copying to tmp table 临时结果集合大于 tmp_table_size。线程把临时表从存储器内部格式改变为磁 盘模式,以节约存储器 Sending data 线程正在为 SELECT 语句处理行,同时正在向客户端发送数据 Sorting for group 线程正在进行分类,以满足 GROUP BY 要求 Sorting for order 线程正在进行分类,以满足 ORDER BY 要求 查询缓存MySQL 内部自带了一个缓存模块。默认是关闭的。主要是因为 MySQL 自带的缓存的应用场景有 限,第一个是它要求 SQL 语句必须一模一样。第二个是表里面任何一条数据发生变化的时候,这张表所 有缓存都会失效。语法解析和预处理(Parser & Preprocessor) 假如随便执行一个字符串 fkdljasklf ,服务器报了一个 1064 的错: [Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'fkdljasklf' at line 1 服务器是怎么知道我输入的内容是错误的? 或者,当我输入了一个语法完全正确的 SQL,但是表名不存在,它是怎么发现的? 这个就是 MySQL 的 Parser 解析器和 Preprocessor 预处理模块。 这一步主要做的事情是对 SQL 语句进行词法和语法分析和语义的解析词法解析 词法分析就是把一个完整的 SQL 语句打碎成一个个的单词。 比如一个简单的 SQL 语句: select name from user where id = 1; 它会打碎成 8 个符号,记录每个符号是什么类型,从哪里开始到哪里结束。语法解析 第二步就是语法分析,语法分析会对 SQL 做一些语法检查,比如单引号有没有闭合,然后根据 MySQL 定义的语法规则,根据 SQL 语句生成一个数据结构。这个数据结构我们把它叫做解析树预处理器(Preprocessor) 如果表名错误,会在预处理器处理时报错。 它会检查生成的解析树,解决解析器无法解析的语义。比如,它会检查表和列名是否存在,检查名 字和别名,保证没有歧义。 4、查询优化(Query Optimizer)与查询执行计划 什么优化器? 问题:一条 SQL 语句是不是只有一种执行方式?或者说数据库最终执行的 SQL 是不是就是我们发 送的 SQL? 这个答案是否定的。一条 SQL 语句是可以有很多种执行方式的。但是如果有这么多种执行方式,这 些执行方式怎么得到的?最终选择哪一种去执行?根据什么判断标准去选择? 这个就是 MySQL 的查询优化器的模块(Optimizer)。 查询优化器的目的就是根据解析树生成不同的执行计划,然后选择一种最优的执行计划,MySQL 里 面使用的是基于开销(cost)的优化器,那种执行计划开销最小,就用哪种。 使用如下命令查看查询的开销: show status like 'Last_query_cost'SHOW VARIABLES LIKE 'optimizer_trace'; set optimizer_trace="enabled=on"; select t.tcid from teacher t,teacher_contact tc where t.tcid = tc.tcid; select * from information_schema.optimizer_trace\G
-
## 导语 OpenMP(Open Multi-Processing)作为一种共享内存的并行编程模型[1],是目前国际上最主流的异构混合并行编程架构之一,自1997年开始推出至今已升级5个主要版本,最新的标准规范为OpenMP API 5.2 [2]。OpenMP API定义了一个可移植、易扩展的编程模型,提供了一系列简单而又灵活的接口(指导语句)用于指导用户简化应用代码和提升性能。OpenMP普遍应用于科学计算领域,如理论物理、化学、材料、气象等领域的数值模拟计算。 毕昇编译器作为一款支持异构并行计算的高性能编译器,支持C/C++/Fortran语言的OpenMP API。目前毕昇编译器基本支持C/C++ OpenMP API 5.0 [3]和Fortran OpenMP API 4.5 [4],并持续提升支持最新标准规范。本文主要讲解毕昇编译器如何使用OpenMP API以及比较常用的指导语句。 ## 使用OpenMP API范例 这里给出一个简单的Fortran代码(test.f90),开启4个线程,并行打印每个线程的信息。 ```fortran program main use omp_lib integer :: id, num_threads call omp_set_num_threads(4) !设置默认创建线程数为4 !$omp parallel private(id) !创建4个线程 id = omp_get_thread_num() !获取当前线程ID num_threads = omp_get_num_threads() !获取当前创建的线程总数 print *, "Hello World Id ", id, " of ", num_threads, " num_threads" !$omp end parallel !结束parallel指导语句,退出并行区域 end ``` 使用毕昇编译器进行编译和运行,结果如下所示: ```shell $ flang test.f90 -fopenmp -o a.out $ ./a.out Hello World Id 3 of 4 num_threads Hello World Id 0 of 4 num_threads Hello World Id 1 of 4 num_threads Hello World Id 2 of 4 num_threads ``` 从结果可以看出,该用例创建了4个线程,并行执行7-9行代码,故print打印语句执行了4次,每个线程打印对应线程的ID。 ## OpenMP常用指导语句 ### parallel指令 parallel指令是最常用的OpenMP API之一,作用是创建一个线程组,parallel指令是进行并行控制的关键。根据OpenMP API标准规范,每个指导语句可以跟随一些子句,用于指导更加详细的功能,比如parallel指令可以跟随if,num_threads,private,firstprivate,shared,default,copyin,reduction,proc_bind,allocate子句。常用子句的详细使用指导如下: - if子句:`if ([parallel:] scalar-logical-expression)`,如果`scalar-logical-expression`的结果为真,则创建一个线程组,含多个线程;如果结果为假,则只创建一个线程。 - num_threads子句:`num_threads (scalar-integer-expression)`,在parallel并行区域内创建多个线程,线程数为`scalar-integer-expression`。 - private子句:`private(list)`,list为一系列变量,list所有变量在并行区域内为私有变量,即每个线程有一份私有拷贝,其它线程不可访问。 - firstprivate子句:`firstprivate(list)`,private子句的升级版,除了定义list所有变量在并行区域内为私有变量,还将进入parallel并行区域前的变量的值赋值给每个线程对应的私有变量。 - shared子句:`shared(list)`,list所有变量共享内存,所有线程均可访问,并不进行拷贝,与并行区域外的同名变量为同一个变量。 > 这里需要注意在并行区域结束处有一个隐含的同步操作,即所有线程都到达并行区域结束处,主线程才会继续执行并行区域外的代码。 ### worksharing-loop指令 worksharing-loop指令是最常用的OpenMP API之一,它的作用是对循环结构进行并行处理。C/C++的指导语句为`#pragma omp for [子句]`,Fortran的指导语句为`!$omp do`。worksharing-loop指令指导接下来的循环被并行执行,子句指导如何被并行执行。worksharing-loop指令可以跟随private,firstprivate,lastprivate,schedule,collapse,nowait,linear,reduction,ordered,allocate。常用子句的详细使用指导如下: - private子句:`private(list)`,用法同parallel的private子句,区别为list变量的范围为循环区域。 - firstprivate子句:`firstprivate(list)`,用法同parallel的private子句,区别为list变量的范围为循环区域。 - lastprivate子句:`lastprivate([lastprivate-modifier:] list)`,private子句的升级版,除了定义list所有变量在并行区域内为私有变量,还将循环最后一次迭代的子线程的私有变量的值赋值给并行区域外同名的原始变量。 - schedule子句:`schedule([modifier[, modifier]:]kind[, chunk_size])`,指导循环的迭代如何调度到创建的线程上,具体调度算法或将在后续文章中详述。 - collapse子句:`collapse(n)`,将指令下的前n层循环进行合并后展开为一个更大的循环,增加可以调度的循环总数。 - nowait子句:`nowait`,在指令结束处不生成隐含的同步操作。如果没有该子句,在指令结束处将生成隐含的同步操作。 ## parallel和worksharing-loop指令的使用范例 ```fortran program main use omp_lib integer, parameter :: N = 10 integer :: i, tid, a(N), b(N) do i = 1, N a(i) = -1 b(i) = -1 enddo !$omp parallel num_threads(N) !$omp do private(tid) firstprivate(a, b) lastprivate(b) do i = 1, N tid = omp_get_thread_num() a(i) = a(i) + i + tid b(i) = b(i) + i + tid if (i == 1 .or. i == N) print *, i, tid, a(i), b(i) enddo !$omp end do !$omp end parallel print *, a print *, b end ``` 该用例与下面的用例等价: ```fortran program main use omp_lib integer, parameter :: N = 10 integer :: i, tid, a(N), b(N) do i = 1, N a(i) = -1 b(i) = -1 enddo !$omp parallel do private(tid) firstprivate(a, b) lastprivate(b) num_threads(N) do i = 1, N tid = omp_get_thread_num() a(i) = a(i) + i + tid b(i) = b(i) + i + tid if (i == 1 .or. i == N) print *, i, tid, a(i), b(i) enddo !$omp end parallel do print *, a print *, b end ``` 使用毕昇编译器进行编译和运行,结果如下所示: ```shell $ flang test.f90 -fopenmp -o a.out $ ./a.out 1 0 0 0 10 9 18 18 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 18 ``` OpenMP指导语句使得该程序将a和b数组在循环内进行私有拷贝,并且将并行区域外的同名变量(a, b)的值(-1)拷贝进并行区域内,计算之后,最后一次迭代的b数组的值(b(10)=18)赋给并行区域外,除此之外,并未改变并行区域外的同名变量(a, b)的值。 ## 后记 毕昇编译器2.1.0版本已发布,进入[毕昇编译器网页](https://www.hikunpeng.com/developer/devkit/compiler/bisheng)下载毕昇编译器可以体验OpenMP并行编程,同时我们提供在线课程和沙箱实验供感兴趣的同学学习使用毕昇编译器。使用过程中遇到问题或者有经验分享,可以前往毕昇论坛或Compiler SIG微信群(添加如下微信小助手进Compiler SIG微信群)进行互动交流。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/26/1645856365210211009.jpg) ## 参考 [1] https://www.openmp.org/ [2] https://www.openmp.org/wp-content/uploads/OpenMP-API-Specification-5-2.pdf [3] https://www.openmp.org/spec-html/5.0/openmp.html [4] https://www.openmp.org/wp-content/uploads/openmp-4.5.pdf ------ 关注 **毕昇编译** 获取编译技术更多信息 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/26/1645856377169914674.jpg)
-
>摘要:通过交换指针的方式实现两个缓冲区的功能互换,十分巧妙,令人称赞。本文分享自华为云社区《[奇妙的双缓冲机制写日志(Java实现)](https://bbs.huaweicloud.com/blogs/324161?utm_source=csdn&utm_medium=bbs-ex&utm_campaign=other&utm_content=content)》,作者: 洛叶飘 。 # 写日志面临的问题 写日志在Web程序中是一个十分基础与常见的需求,其对性能的要求很高。主要需要处理以下问题: 1. 多线程并发,需要保证顺序性。 2. 高配IO操作,但IO操作相比其他指令耗时长,性能低。 即一方面需要面对程序端高配的日志写请求,一方面需要受限于系统磁盘相对缓慢写入文件,应该如何处理呢。 # 双缓冲区 因此,引入双缓冲区机制,一个缓冲区存储应用程序端发送的日志,按照时间顺序依次存储;另一个缓冲区负责向低层磁盘发送写文件请求。 写文件请求执行相对较慢,因此当写文件执行完毕后,通知管理程序,此时可以将另一个缓冲区内容写入磁盘了。 双缓冲区的奇妙之处就在于,两个缓冲区的交换,是通过交换指针来实现的,非常的高效。 部分实现代码如下(其他部分逻辑代码已省略)。 ## 双缓冲区代码,不使用Java现有的线程安全类,后续通过加锁保证数据安全。 // 负责接收应用程序发来的日志 LinkedList currentBuffer = new LinkedList(); // 负责将数据同步到磁盘 LinkedList syncBuffer = new LinkedList(); ## 第一个缓冲区,接收应用程序高速写日志请求 public void log(String content) { // 加锁保证第一个缓冲区 synchronized(this) { // 将log写入内存缓冲中,这里不会直接刷入磁盘文件 currentBuffer.add(content); } // 将缓冲区中的内容刷到磁盘 logSync(); } ## 第二缓冲区,向磁盘写日志,并在写入后交换缓冲区指针 private void logSync() { synchronized(this) { // 当前在刷内存缓冲到磁盘中去 if (isSyncRunning) { // 判断是否第二个缓冲区还在刷 while (isSyncRunning) { try { // 释放锁,即允许第一个缓冲区继续接收日志缓存, 然后等待被唤醒 wait(2000); } catch (Exception e) { e.printStackTrace(); } } // 此时没有人在写磁盘 } // 交换缓冲区指针 setReadyToSync(); // 设置当前正在同步到磁盘的标志位 isSyncRunning = true; } // 刷磁盘,性能最低,不能加锁 logBuffer.flush(); synchronized(this) { // 同步完磁盘之后,将标志位复位 isSyncRunning = false; // 唤醒其他等待刷磁盘的线程 notifyAll(); } } ## 交换缓冲区指针,功能变更 public void setReadyToSync() { LinkedList tmp = currentBuffer; currentBuffer = syncBuffer; syncBuffer = tmp; } # 奇妙之处 ## 两个缓冲区各自处理,互不干扰 两个缓冲区很好的解决了应用程序的“**快速、多线程**”与IO操作的“**缓慢,单线程**”的矛盾。应该说,引入双缓冲区是一个显而易见的方式。 ## 缓冲区交换 通过交换指针的方式实现两个缓冲区的功能互换,十分巧妙,令人称赞。 # 总结 你知道吗?电视机里也在用着双缓冲机制
-
管道:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。命名管道FIFO:未命名的管道只能在两个相关的进程之间通信,通过命名管道FIFO,不相关的进程也能交换数据。消息队列:消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。消息队列允许一个或多个进程向它写入与读取消息。管道和命名管道的通信数据都是先进先出原则,消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取,比FIFO更有优势。共享内存:共享内存是允许一个或多个进程共享的一块内存区域。信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。
-
1 介绍 HPL(The High-Performance Linpack Benchmark)是测试高性能计算集群系统浮点性能的基准。HPL通过对高性能计算集群采用高斯消元法求解一元N次稠密线性代数方程组的测试,评价高性能计算集群的浮点计算能力。 建议的版本 建议使用版本为“hpl-2.3”。2 编译安装依赖库2.1 安装毕昇编译器操作步骤参考《毕昇编译器用户指南》中“安装毕昇编译器”章节--结束2.2 安装Hyper MPI操作步骤参考《华为高性能通信库用户指南》中“源码安装Hyper MPI”章节--结束2.3 安装openblas操作步骤步骤 1 执行以下命令下载openblas安装包。 wget https://github.com/xianyi/OpenBLAS/archive/v0.3.6.tar.gz步骤 2 执行以下命令解压openblas安装包。 tar -xvf OpenBLAS-0.3.6.tar.gz步骤 3 执行以下命令进入解压后的目录。 cd OpenBLAS-0.3.6步骤 4 执行以下命令设置环境变量 export CC=clang export CXX=clang++ export FC=flang步骤 5 执行以下命令进行编译安装。 make make PREFIX=/path/to/OPENBLAS install3 编译安装主程序操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令下载主程序安装包。 wget https://www.netlib.org/benchmark/hpl/hpl-2.3.tar.gz步骤 3 执行以下命令解压主程序安装包。 tar -xvf hpl-2.3.tar.gz步骤 4 执行以下命令进入解压后的目录。 cd hpl-2.3步骤 5 创建文件:Linux_Arm,增加如下内容。SHELL = /bin/shCD = cdCP = cpLN_S = ln -fsMKDIR = mkdir -pRM = /bin/rm -fTOUCH = touch ARCH = Linux_ArmHOME = /path/to/HPLTOPdir = $(HOME)/hpl-2.3INCdir = $(TOPdir)/includeBINdir = $(TOPdir)/bin/$(ARCH)LIBdir = $(TOPdir)/lib/$(ARCH)HPLlib = $(LIBdir)/libhpl.a LAdir = /path/to/OPENBLASifndef LAincLAinc = $(LAdir)/includeendififndef LAlibLAlib = /path/to/OPENBLAS/lib/libopenblas.aendif F2CDEFS = -DAdd__ -DF77_INTEGER=int -DstringSunStyleHPL_INCLUDES = -I$(INCdir) -I$(INCdir)/$(ARCH) -I$(LAinc) $(MPinc)HPL_LIBS = $(HPLlib) $(LAlib) $(MPlib)HPL_OPTS = -DHPL_DETAILED_TIMING -DHPL_PROGRESS_REPORTHPL_DEFS = $(F2CDEFS) $(HPL_OPTS) $(HPL_INCLUDES) CC = mpiccCCNOOPT = $(HPL_DEFS)OMP_DEFS = -fopenmpCCFLAGS = $(HPL_DEFS) -O3LINKER = $(CC)LINKFLAGS = $(CCFLAGS) $(OMP_DEFS) ARCHIVER = arARFLAGS = rRANLIB = echo步骤 6 执行以下命令进行编译安装。 make arch=Linux_Arm 编译完成后可以看到在当前目录下生成bin,lib,include三个文件夹4 运行和验证操作步骤步骤 1 执行以下命令进入HPL测试目录 cd /path/to/HPL/hpl-2.3/bin/Linux_Arm步骤 2 修改dat文件6、8、11、12行 234624 Ns 192 NBs 2 Ps 8 QsN表示求解的矩阵数量与规模。矩阵规模N越大,有效计算所占的比例也越大,系统浮点处理性能也就越高。但矩阵规模越大会导致内存消耗量越多,如果系统实际内存空间不足,使用缓存、性能会大幅度降低。矩阵占用系统总内存的80%左右为最佳,即N×N×8=系统总内存×80%(其中总内存的单位为字节)。 上述N值是以512G系统内存为例计算得到的,根据经验为384的倍数更佳。NB表示求解矩阵过程中矩阵分块的大小,分块大小对性能有很大的影响,NB的选择和软硬件许多因素密切相关。NB值的选择主要是通过实际测试得出最优值,一般遵循以下规律: NB不能太大或太小,一般小于384。 NB×8一定是缓存行的倍数。 NB的大小和通信方式、矩阵规模、网络、处理器速度等有关系。 一般通过单节点或单CPU测试可以得到几个较好的NB值,但当系统规模增加、问题规模变大,有些NB取值所得性能会下降。因此建议在小规模测试时选择3个性能不错的NB值,再通过大规模测试检验这些选择。P表示水平方向处理器个数,Q表示垂直方向处理器个数。P×Q表示二维处理器网格。P×Q=进程数。P和Q的取值一般遵循以下规律: P≤Q,一般情况下P的取值小于Q,因为列向通信量(通信次数和通信数据量)要远大于横向通信。 P建议选择2的幂。HPL中水平方向通信采用二元交换法(Binary Exchange),当水平方向处理器个数P为2的幂时性能最优。 以128核机器为例,经测算当进程为16时,每个进程8个线程时性能较好,故此处P=2 Q=8。步骤 3 执行以下命令运行HPL测试 time mpirun --allow-run-as-root -mca pml ucx -mca btl ^vader,tcp,openib,uct -x UCX_TLS=self,sm --bind-to core --rank-by core -map-by ppr:16:node:pe=8 ./xhpl ----结束
-
关于CM通信的一个Method例子,在看代码时候有一点疑惑,autosar cm通信使用proxy--skeleton机制,在client侧创建proxy的时候,都采用了信号量mutex,client实现为c++类,运行时候在main中创建该类的实例,使用一个线程驱动这个类实例运行。我的疑惑是mutex实现为client类的protected类非static成员,那么client实例化以后就是每个类实例都有自己的mutex实例,那么std::lock_guard<std::mutex> lock(proxyMutex_);语句如何起到保护底层proxy数据(我理解是为了多个client实例下保护底层proxy数据实例)的作用?一般mutex保护一个数据区,都是定义一个和数据区对应的全局mutex实例,多个线程在访问数据全之前先lock这个mutex,操作完数据区以后释放。我看了代码,所有cm例子代码都是这么写的,我就有点疑惑了,按理说client类mutex应该定位static类型才能起到保护作用,如果每个类都定义普通mutex实例,如何起到保护作用?
-
锁是GaussDB(DWS)实现并发管理的关键要素,GaussDB(DWS)锁类别有表级锁、分区级锁(和表级锁一致)、事务锁、咨询锁等,当前业务最常用的是表级锁、分区级锁(和表级锁一致)、事务锁。不同的SQL语句执行时需要申请并持有对应的锁,当这些锁资源存在互斥时,对应的业务SQL就会产生等待;这种等待会产生下面几种后果持锁的一方释放锁(一般对应的动作为持锁的事物提交),等待锁的一方申请到锁,然后继续执行持锁的一方事物长时间未提交,等待锁的一方因为锁等待超时导致作业报错A实例上持锁事物和申请锁的事物在B实例上角色互换,产生分布式死锁(具体见下文介绍)。这种场景下需要首先达到锁等待超时的事物报错回滚时释放锁资源,然后另外一个事物申请到才能正常进行从上述的描述可以看到,锁等待特别是分布式死锁对业务影响很大,轻则产生等待导致业务性能抖动和下降,甚至业务报错。GaussDB(DWS)提供了两个集群级别的视图快速识别和查询锁等待和分布式死锁信息,可实现此类问题的秒级问题的定位和分析1)锁等待检测视图pgxc_lock_conflicts【功能】查询当前库里面不同节点上的锁等待信息字段名称数据类型字段描述locktypetext被锁定对象的类型,当前支持relation、partition、transactionid三种类型的锁类型nodenamename被锁定对象的节点的名称dbnamename被锁定对象的数据库的名称。如果被锁定对象是事务,则为NULLnspnamename被锁定对象的命名空间的名称relnamename被锁定对象对应的relation的名称。如果被锁定对象既不是relation,也不是relation的一部分,则为NULLpartnamename被锁定对象对应的分区的名称。如果被锁定对象不是分区,则为NULLpageinteger被锁定对象对应的页面的编号。如果被锁定对象既不是页面,也不是元组,则为NULLtuplesmallint被锁定对象对应的元组的编号。如果被锁定对象不是元组,则为NULLtransactionidxid被锁定对象对应的事务的ID。如果被锁定对象不是事务,则为NULLusernamename当前线程所处session的用户名,一般也是当前线程执行语句的用户名称gxidxid当前线程所处事物的事物IDxactstarttimestamptz当前线程所处事务的事物开始时间queryidbigint申请锁的线程的最新查询的queryidquerytext申请锁的线程的最新查询语句pidbigint申请锁的线程的IDmodetext锁的级别grantedbooleanTRUE表示当前线程持有上述指定的锁FALSE表示当前线程正在申请锁,但是没有申请上,处在等待锁的状态【解析】执行如下查询结果postgres=# SELECT * FROM pgxc_lock_conflicts ORDER BY nodename,dbname,locktype,nspname,relname,partname; locktype | nodename | dbname | nspname | relname | partname | page | tuple | transactionid | username | gxid | xactstart | queryid | query | pid | mode | granted -----------+----------+----------+---------+-----------------------+----------+------+-------+---------------+-----------+----------+-------------------------------+--------------------+----------------------------------------------------------+-----------------+---------------------+--------- partition | cn_5001 | postgres | public | table_partition_num_3 | p1 | | | | dfm | 24097147 | 2022-02-17 17:56:03.113194+08 | 104145741383084190 | alter table table_partition_num_3 truncate partition p1; | 140160505136896 | AccessExclusiveLock | f partition | cn_5001 | postgres | public | table_partition_num_3 | p1 | | | | dfm | 24102679 | 2022-02-17 18:41:36.580348+08 | 0 | alter table table_partition_num_3 truncate partition p1; | 140160568055552 | AccessExclusiveLock | t relation | cn_5002 | postgres | public | xxx | | | | | dfm | 24102679 | 2022-02-17 18:41:36.580348+08 | 175921860444402398 | truncate xxx; | 140418767369984 | AccessShareLock | f relation | cn_5002 | postgres | public | xxx | | | | | dfm | 24097147 | 2022-02-17 17:56:03.113194+08 | 0 | truncate xxx; | 140420489144064 | AccessExclusiveLock | t (4 rows)如上的SQL显示在节点cn_5001的postgres里面的表public.table_partition_num_3的分区p1上存在分区级别(partition)的锁冲突。在当前的锁冲突中线程140160568055552持有锁(mode = true),锁级别是AccessExclusiveLock,执行语句为alter table table_partition_num_3 truncate partition p1。线程140160568055552在等待(mode = false)AccessExclusiveLock锁,等待锁的语句也是alter table table_partition_num_3 truncate partition p1。在节点cn_5002的postgres里面的表public.xxx上存在表级别(relation)的锁冲突。线程140420489144064持有锁AccessExclusiveLock(mode = true),线程140418767369984在等待(mode = false)AccessShareLock锁2)分布式锁等待检测视图pgxc_deadlock【功能】查询当前库里面不同节点上的分布式死锁信息字段名称数据类型字段描述locktypetext被锁定对象的类型,当前支持relation、partition、transactionid三种类型的锁类型nodenamename被锁定对象的节点的名称dbnamename被锁定对象的数据库的名称。如果被锁定对象是事务,则为NULLnspnamename被锁定对象的命名空间的名称relnamename被锁定对象对应的关系的名称。如果被锁定对象既不是关系,也不是关系的一部分,则为NULLpartnamename被锁定对象对应的分区的名称。如果被锁定对象不是分区,则为NULLpageinteger被锁定对象对应的页面的编号。如果被锁定对象既不是页面,也不是元组,则为NULLtuplesmallint被锁定对象对应的元组的编号。如果被锁定对象不是元组,则为NULLtransactionidxid被锁定对象对应的事务的ID。如果被锁定对象不是事务,则为NULLwaitusernamename等待锁的用户的名称waitgxidxid等待锁的事务的IDwaitxactstarttimestamptz等待锁的事务的开始时间waitqueryidbigint等待锁的线程的最新查询IDwaitquerytext等待锁的线程的最新查询语句waitpidbigint等待锁的线程的IDwaitmodetext等待的锁的级别holdusernamename持有锁的用户的名称holdgxidxid持有锁的事务的IDholdxactstarttimestamptz持有锁的事务的开始时间holdqueryidbigint持有锁的线程的最新查询IDholdquerytext持有锁的线程的最新查询语句holdpidbigint持有锁的线程的IDholdmodetext持有的锁的级别【解析】执行如下查询结果postgres=# SELECT * FROM pgxc_deadlock ORDER BY nodename,dbname,locktype,nspname,relname,partname; locktype | nodename | dbname | nspname | relname | partname | page | tuple | transactionid | waitusername | waitgxid | waitxactstart | waitqueryid | waitquery | waitpid | waitmode | holdusername | holdgxid | holdxactstart | holdqueryid | holdquery | holdpid | holdmode ----------+----------+----------+---------+---------+----------+------+-------+---------------+--------------+----------+-------------------------------+--------------------+-----------------------------------------------------+-----------------+-----------------+--------------+----------+-------------------------------+-------------+--------------+-----------------+--------------------- relation | cn_5001 | postgres | public | t2 | | | | | j00565968 | 24112406 | 2022-02-17 20:01:57.421532+08 | 104145741383110084 | EXECUTE DIRECT ON(dn_6003_6004) 'SELECT * FROM t2'; | 140160505136896 | AccessShareLock | j00565968 | 24112465 | 2022-02-17 20:02:24.220656+08 | 0 | TRUNCATE t2; | 140160421234432 | AccessExclusiveLock relation | cn_5002 | postgres | public | t1 | | | | | j00565968 | 24112465 | 2022-02-17 20:02:24.220656+08 | 175921860444446866 | EXECUTE DIRECT ON(dn_6001_6002) 'SELECT * FROM t1'; | 140418784151296 | AccessShareLock | j00565968 | 24112406 | 2022-02-17 20:01:57.421532+08 | 0 | TRUNCATE t1; | 140421763163904 | AccessExclusiveLock (2 rows)如上的SQL显示,在postgres库里面节点cn_5001上 事务24112465通过线程140160421234432持有表public.t2的AccessExclusiveLock锁 事务24112406通过线程140160505136896在等待申请表public.t2的AccessShareLock锁节点cn_5002上 事务24112465通过线程140418784151296在等待申请表public.t1的AccessShareLock锁 事物24112406通过线程140421763163904持有表public.t1的AccessExclusiveLock锁如果我们把资源的持有情况按照持有到申请定义一个防线的话,可以形成如下表格节点事务24112465方向事务24112406cn_5001持有表public.t2的AccessExclusiveLock锁→申请表public.t2的AccessShareLock锁cn_5002申请表public.t1的AccessShareLock锁←持有表public.t1的AccessExclusiveLock锁从上述可以看出,事务24112465在节点cn_5001持有表public.t2的AccessExclusiveLock锁,等待申请申请表public.t1的AccessShareLock锁;事务24112406在节点cn_5002上持有表public.t1的AccessExclusiveLock锁,等待申请申请表public.t2的AccessShareLock锁;事务24112406和事务24112465只有等待彼此提交才能申请到锁资源,让自己继续执行,这种在多个实例上的分布式等待关系形成了一个环状,我们称这种现象为分布式死锁。3) 锁等待和分布式死锁的区别对于分布式死锁,只能一个事务因为锁等待(参数lockwait_timeout)超时回滚的时候,另外一个事务才能进行下去;或者人工干预kill或者cancel其中一个事务,让另外一个事务进行下去。对于没有分布式死锁的锁等待,这种一般不需要人工干涉,等待持锁事务正常执行完成之后另外一个事务就可以正常执行;但是如果事务持锁时间超过锁等待超时参数(参数lockwait_timeout),等待锁的事务会因为锁等待超时失败。
-
锁是GaussDB(DWS)实现并发管理的关键要素,GaussDB(DWS)锁类别有表级锁、分区级锁(和表级锁一致)、事务锁、咨询锁等,当前业务最常用的是表级锁、分区级锁(和表级锁一致)、事务锁。不同的SQL语句执行时需要申请并持有对应的锁,当这些锁资源存在互斥时,对应的业务SQL就会产生等待;这种等待会产生下面几种后果持锁的一方释放锁(一般对应的动作为持锁的事物提交),等待锁的一方申请到锁,然后继续执行持锁的一方事物长时间未提交,等待锁的一方因为锁等待超时导致作业报错A实例上持锁事物和申请锁的事物在B实例上角色互换,产生分布式死锁(具体见下文介绍)。这种场景下需要首先达到锁等待超时的事物报错回滚时释放锁资源,然后另外一个事物申请到才能正常进行从上述的描述可以看到,锁等待特别是分布式死锁对业务影响很大,轻则产生等待导致业务性能抖动和下降,甚至业务报错。GaussDB(DWS)提供了两个集群级别的视图快速识别和查询锁等待和分布式死锁信息,可实现此类问题的秒级问题的定位和分析1)锁等待检测视图pgxc_lock_conflicts【功能】查询当前库里面不同节点上的锁等待信息字段名称数据类型字段描述locktypetext被锁定对象的类型,当前支持relation、partition、transactionid三种类型的锁类型nodenamename被锁定对象的节点的名称dbnamename被锁定对象的数据库的名称。如果被锁定对象是事务,则为NULLnspnamename被锁定对象的命名空间的名称relnamename被锁定对象对应的relation的名称。如果被锁定对象既不是relation,也不是relation的一部分,则为NULLpartnamename被锁定对象对应的分区的名称。如果被锁定对象不是分区,则为NULLpageinteger被锁定对象对应的页面的编号。如果被锁定对象既不是页面,也不是元组,则为NULLtuplesmallint被锁定对象对应的元组的编号。如果被锁定对象不是元组,则为NULLtransactionidxid被锁定对象对应的事务的ID。如果被锁定对象不是事务,则为NULLusernamename当前线程所处session的用户名,一般也是当前线程执行语句的用户名称gxidxid当前线程所处事物的事物IDxactstarttimestamptz当前线程所处事务的事物开始时间queryidbigint申请锁的线程的最新查询的queryidquerytext申请锁的线程的最新查询语句pidbigint申请锁的线程的IDmodetext锁的级别grantedbooleanTRUE表示当前线程持有上述指定的锁FALSE表示当前线程正在申请锁,但是没有申请上,处在等待锁的状态【解析】执行如下查询结果postgres=# SELECT * FROM pgxc_lock_conflicts ORDER BY nodename,dbname,locktype,nspname,relname,partname; locktype | nodename | dbname | nspname | relname | partname | page | tuple | transactionid | username | gxid | xactstart | queryid | query | pid | mode | granted -----------+----------+----------+---------+-----------------------+----------+------+-------+---------------+-----------+----------+-------------------------------+--------------------+----------------------------------------------------------+-----------------+---------------------+--------- partition | cn_5001 | postgres | public | table_partition_num_3 | p1 | | | | dfm | 24097147 | 2022-02-17 17:56:03.113194+08 | 104145741383084190 | alter table table_partition_num_3 truncate partition p1; | 140160505136896 | AccessExclusiveLock | f partition | cn_5001 | postgres | public | table_partition_num_3 | p1 | | | | dfm | 24102679 | 2022-02-17 18:41:36.580348+08 | 0 | alter table table_partition_num_3 truncate partition p1; | 140160568055552 | AccessExclusiveLock | t relation | cn_5002 | postgres | public | xxx | | | | | dfm | 24102679 | 2022-02-17 18:41:36.580348+08 | 175921860444402398 | truncate xxx; | 140418767369984 | AccessShareLock | f relation | cn_5002 | postgres | public | xxx | | | | | dfm | 24097147 | 2022-02-17 17:56:03.113194+08 | 0 | truncate xxx; | 140420489144064 | AccessExclusiveLock | t (4 rows)如上的SQL显示在节点cn_5001的postgres里面的表public.table_partition_num_3的分区p1上存在分区级别(partition)的锁冲突。在当前的锁冲突中线程140160568055552持有锁(mode = true),锁级别是AccessExclusiveLock,执行语句为alter table table_partition_num_3 truncate partition p1。线程140160568055552在等待(mode = false)AccessExclusiveLock锁,等待锁的语句也是alter table table_partition_num_3 truncate partition p1。在节点cn_5002的postgres里面的表public.xxx上存在表级别(relation)的锁冲突。线程140420489144064持有锁AccessExclusiveLock(mode = true),线程140418767369984在等待(mode = false)AccessShareLock锁2)分布式锁等待检测视图pgxc_deadlock【功能】查询当前库里面不同节点上的分布式死锁信息字段名称数据类型字段描述locktypetext被锁定对象的类型,当前支持relation、partition、transactionid三种类型的锁类型nodenamename被锁定对象的节点的名称dbnamename被锁定对象的数据库的名称。如果被锁定对象是事务,则为NULLnspnamename被锁定对象的命名空间的名称relnamename被锁定对象对应的关系的名称。如果被锁定对象既不是关系,也不是关系的一部分,则为NULLpartnamename被锁定对象对应的分区的名称。如果被锁定对象不是分区,则为NULLpageinteger被锁定对象对应的页面的编号。如果被锁定对象既不是页面,也不是元组,则为NULLtuplesmallint被锁定对象对应的元组的编号。如果被锁定对象不是元组,则为NULLtransactionidxid被锁定对象对应的事务的ID。如果被锁定对象不是事务,则为NULLwaitusernamename等待锁的用户的名称waitgxidxid等待锁的事务的IDwaitxactstarttimestamptz等待锁的事务的开始时间waitqueryidbigint等待锁的线程的最新查询IDwaitquerytext等待锁的线程的最新查询语句waitpidbigint等待锁的线程的IDwaitmodetext等待的锁的级别holdusernamename持有锁的用户的名称holdgxidxid持有锁的事务的IDholdxactstarttimestamptz持有锁的事务的开始时间holdqueryidbigint持有锁的线程的最新查询IDholdquerytext持有锁的线程的最新查询语句holdpidbigint持有锁的线程的IDholdmodetext持有的锁的级别【解析】执行如下查询结果postgres=# SELECT * FROM pgxc_deadlock ORDER BY nodename,dbname,locktype,nspname,relname,partname; locktype | nodename | dbname | nspname | relname | partname | page | tuple | transactionid | waitusername | waitgxid | waitxactstart | waitqueryid | waitquery | waitpid | waitmode | holdusername | holdgxid | holdxactstart | holdqueryid | holdquery | holdpid | holdmode ----------+----------+----------+---------+---------+----------+------+-------+---------------+--------------+----------+-------------------------------+--------------------+-----------------------------------------------------+-----------------+-----------------+--------------+----------+-------------------------------+-------------+--------------+-----------------+--------------------- relation | cn_5001 | postgres | public | t2 | | | | | j00565968 | 24112406 | 2022-02-17 20:01:57.421532+08 | 104145741383110084 | EXECUTE DIRECT ON(dn_6003_6004) 'SELECT * FROM t2'; | 140160505136896 | AccessShareLock | j00565968 | 24112465 | 2022-02-17 20:02:24.220656+08 | 0 | TRUNCATE t2; | 140160421234432 | AccessExclusiveLock relation | cn_5002 | postgres | public | t1 | | | | | j00565968 | 24112465 | 2022-02-17 20:02:24.220656+08 | 175921860444446866 | EXECUTE DIRECT ON(dn_6001_6002) 'SELECT * FROM t1'; | 140418784151296 | AccessShareLock | j00565968 | 24112406 | 2022-02-17 20:01:57.421532+08 | 0 | TRUNCATE t1; | 140421763163904 | AccessExclusiveLock (2 rows)如上的SQL显示,在postgres库里面节点cn_5001上 事务24112465通过线程140160421234432持有表public.t2的AccessExclusiveLock锁 事务24112406通过线程140160505136896在等待申请表public.t2的AccessShareLock锁节点cn_5002上 事务24112465通过线程140418784151296在等待申请表public.t1的AccessShareLock锁 事物24112406通过线程140421763163904持有表public.t1的AccessExclusiveLock锁如果我们把资源的持有情况按照持有到申请定义一个防线的话,可以形成如下表格节点事务24112465方向事务24112406cn_5001持有表public.t2的AccessExclusiveLock锁→申请表public.t2的AccessShareLock锁cn_5002申请表public.t1的AccessShareLock锁←持有表public.t1的AccessExclusiveLock锁从上述可以看出,事务24112465在节点cn_5001持有表public.t2的AccessExclusiveLock锁,等待申请申请表public.t1的AccessShareLock锁;事务24112406在节点cn_5002上持有表public.t1的AccessExclusiveLock锁,等待申请申请表public.t2的AccessShareLock锁;事务24112406和事务24112465只有等待彼此提交才能申请到锁资源,让自己继续执行,这种在多个实例上的分布式等待关系形成了一个环状,我们称这种现象为分布式死锁。3) 锁等待和分布式死锁的区别对于分布式死锁,只能一个事务因为锁等待(参数lockwait_timeout)超时回滚的时候,另外一个事务才能进行下去;或者人工干预kill或者cancel其中一个事务,让另外一个事务进行下去。对于没有分布式死锁的锁等待,这种一般不需要人工干涉,等待持锁事务正常执行完成之后另外一个事务就可以正常执行;但是如果事务持锁时间超过锁等待超时参数(参数lockwait_timeout),等待锁的事务会因为锁等待超时失败。
-
原文链接:https://blog.csdn.net/m0_61531676/article/details/122906069?spm=1000.2115.3001.5927CPU 、GPU 、内存和多进程架构在这个由 4 部分组成的博客系列中,我们将深入了解 Chrome 浏览器,从高级架构到渲染管道的细节。如果您想知道浏览器如何将您的代码变成功能性网站,或者您不确定为什么建议使用特定技术来提高性能,那么本系列适合您。作为本系列的第 1 部分,我们将了解核心计算术语和 Chrome 的多进程架构。注意:如果您熟悉 CPU或GPU 和进程和线程的概念,您可以直接跳到 浏览器架构 那一节。计算机的核心是 CPU 和 GPU为了了解浏览器运行的环境,我们需要了解一些计算机部件以及它们的作用。中央处理器——CPU首先是中央处理单元或CPU 。CPU可以被认为是您计算机的大脑。一个 CPU 内核,在这里被描绘成一个办公室的工作人员,可以在他们进来时一个一个地处理许多不同的任务。CPU可以处理从数学到艺术的所有事情,同时知道如何回复客户的电话。过去,大多数 CPU 都是单芯片。内核就像另一个 CPU 生活在同一个芯片中。在现代硬件中,您通常会获得多个内核,从而为您的手机和笔记本电脑提供更多的算力。图形处理器——GPU图形处理单元(也就是GPU)是计算机的另一部分。与 CPU 不同,GPU 擅长处理简单的任务,但可以同时跨多个内核。顾名思义,它最初是为处理图形而开发的。这就是为什么在图形上下文中“使用 GPU”或“GPU 支持”与快速渲染和流畅交互相关联。近年来,随着 GPU 加速计算的发展,越来越多的计算在 GPU 上成为可能。当您在计算机或手机上启动应用程序时,CPU 和 GPU 是应用程序的动力。通常,应用程序使用操作系统提供的机制在 CPU 和 GPU 上运行。在进程和线程上执行程序在深入研究浏览器架构之前要掌握的另一个概念是进程和线程。进程可以描述为应用程序的执行程序。线程是存在于进程内部并执行其进程程序的任何部分的线程。当您启动应用程序时,会创建一个进程。该程序可能会创建线程来帮助它工作,但这是可选的。操作系统为进程提供了一块可使用的内存“平板”,所有应用程序状态都保存在该私有内存空间中。当您关闭应用程序时,这个进程也会消失,并且操作系统会释放内存。一个进程可以要求操作系统启动另一个进程来运行不同的任务。发生这种情况时,将为新进程分配内存的不同部分。如果两个进程需要通信,它们可以通过使用进程间通信( IPC )来实现。许多应用程序被设计为以这种方式工作,因此如果一个工作进程没有响应,它可以在不停止运行应用程序不同部分的其他进程的情况下重新启动。浏览器架构那么如何使用进程和线程构建 Web 浏览器呢?好吧,它可能是一个具有许多不同线程的进程,也可能是许多不同的进程,其中有几个线程通过 IPC 进行通信。这里要注意的重要一点是,这些不同的架构是实现细节。没有关于如何构建 Web 浏览器的标准规范。一种浏览器的方法可能与另一种完全不同。为了这个博客系列,我们将使用下图中描述的 Chrome 的最新架构。顶部是浏览器进程与处理应用程序不同部分的其他进程协调。对于渲染器进程,会创建多个进程并将其分配给每个选项卡。直到最近,Chrome 还在可能的情况下为每个选项卡提供了一个进程;现在它尝试为每个站点提供自己的进程,包括 iframe(请参阅站点隔离那一节)。什么进程控制什么?下表描述了每个 Chrome 进程及其控制的内容:进程控制的内容浏览器控制应用程序的“chrome”部分,包括地址栏、书签、后退和前进按钮。还处理网络浏览器中不可见的特权部分,例如网络请求和文件访问。渲染器控制显示网站的选项卡内的任何内容。插件控制网站使用的所有插件,例如 Flash。图形处理器GPU与其他进程隔离处理 GPU 任务。它被分成不同的进程,因为 GPU 处理来自多个应用程序的请求并将它们绘制在同一个表面上。还有更多进程,例如扩展进程和实用程序进程。如果您想查看 Chrome 中正在运行的进程数量,请单击选项菜单图标 more_vert在右上角,选择更多工具,然后选择任务管理器。这将打开一个窗口,其中列出了当前正在运行的进程以及它们正在使用多少 CPU/内存。Chrome 中多进程架构的好处之前,我提到 Chrome 使用多个渲染器进程。在最简单的情况下,您可以想象每个选项卡都有自己的渲染器进程。假设您打开了 3 个选项卡,每个选项卡都由一个独立的渲染器进程运行。如果一个选项卡变得无响应,那么您可以关闭无响应的选项卡并继续前进,同时保持其他选项卡处于活动状态。如果所有选项卡都在一个进程上运行,则当一个选项卡无响应时,所有选项卡均无响应。这样就很可悲。浏览器的工作分成多个进程的另一个好处是安全性和沙盒。由于操作系统提供了一种限制进程权限的方法,因此浏览器可以对某些进程的某些功能进行沙盒处理。例如,Chrome 浏览器限制处理任意用户输入的进程(如渲染器进程)的任意文件访问。因为进程有自己的私有内存空间,它们通常包含通用基础设施的副本(例如 V8,它是 Chrome 的 JavaScript 引擎)。这意味着更多的内存使用,因为它们不能像在同一进程中的线程那样共享。为了节省内存,Chrome 限制了它可以启动的进程数。限制取决于您的设备拥有多少内存和 CPU 能力,但是当 Chrome 达到限制时,它会开始在一个进程中运行来自同一站点的多个选项卡。节省更多内存 - Chrome 中的服务化相同的方法适用于浏览器进程。Chrome 正在经历架构更改,以将浏览器程序的每个部分作为服务运行,从而可以轻松拆分为不同的进程或聚合为一个。一般的想法是,当 Chrome 在强大的硬件上运行时,它可能会将每个服务拆分为不同的进程以提供更高的稳定性,但如果它在资源受限的设备上,Chrome 会将服务整合到一个进程中以节省内存占用。在此更改之前,已在 Android 等平台上使用了类似的整合流程以减少内存使用的方法。每帧渲染器进程 - 站点隔离站点隔离是 Chrome 中最近引入的一项功能,它为每个跨站点 iframe 运行单独的渲染器进程。我们一直在讨论每个选项卡模型一个渲染器进程,它允许跨站点 iframe 在单个渲染器进程中运行,并在不同站点之间共享内存空间。在同一个渲染器进程中运行 a.com 和 b.com 似乎没问题。同源策略 是网络的核心安全模型;它确保一个站点在未经同意的情况下无法访问其他站点的数据。绕过此策略是安全攻击的主要目标。进程隔离是分隔站点的最有效方法。随着崩溃和幽灵,更明显的是,我们需要使用进程来分隔站点。自 Chrome 67 起,默认情况下在桌面上启用站点隔离,选项卡中的每个跨站点 iframe 都会获得一个单独的渲染器进程。启用站点隔离是一项多年的工程工作。站点隔离并不像分配不同的渲染器进程那么简单;它从根本上改变了 iframe 相互通信的方式。在不同进程上运行 iframe 的页面上打开 devtools 意味着 devtools 必须实施幕后工作以使其看起来无缝衔接。即使运行一个简单的 Ctrl+F 在页面中查找单词也意味着在不同的渲染器进程中进行搜索。您可以看到浏览器工程师将 Site Isolation 的发布称为重要里程碑的原因!结语在这篇文章中,我们介绍了浏览器架构的高级视图,并介绍了多进程架构的好处。我们还介绍了与多进程架构密切相关的 Chrome 中的服务化和站点隔离。在下一篇文章中,我们将开始深入研究这些进程和线程之间发生的事情,以便显示一个网站。
-
这篇总结主要是基于我之前JVM系列文章而形成的的。主要是把重要的知识点用自己的话说了一遍,可能会有一些错误,还望见谅和指点。谢谢#更多详细内容可以查看我的专栏文章:深入理解JVM虚拟机https://blog.csdn.net/column/details/21960.htmlJVM介绍和源码首先JVM是一个虚拟机,当你安装了jre,它就包含了jvm环境。JVM有自己的内存结构,字节码执行引擎,因此class字节码才能在jvm上运行,除了Java以外,Scala,groovy等语言也可以编译成字节码而后在jvm中运行。JVM是用c开发的。JVM内存模型内存模型老生常谈了,主要就是线程共享的堆区,方法区,本地方法栈。还有线程私有的虚拟机栈和程序计数器。堆区存放所有对象,每个对象有一个地址,Java类jvm初始化时加载到方法区,而后会在堆区中生成一个Class对象,来负责这个类所有实例的实例化。栈区存放的是栈帧结构,栈帧是一段内存空间,包括参数列表,返回地址,局部变量表等,局部变量表由一堆slot组成,slot的大小固定,根据变量的数据类型决定需要用到几个slot。方法区存放类的元数据,将原来的字面量转换成引用,当然,方法区也提供常量池,常量池存放-128到127的数字类型的包装类。字符串常量池则会存放使用intern的字符串变量。JVM OOM和内存泄漏这里指的是oom和内存泄漏这类错误。oom一般分为三种,堆区内存溢出,栈区内存溢出以及方法区内存溢出。堆内存溢出主要原因是创建了太多对象,比如一个集合类死循环添加一个数,此时设置jvm参数使堆内存最大值为10m,一会就会报oom异常。栈内存溢出主要与栈空间和线程有关,因为栈是线程私有的,如果创建太多线程,内存值超过栈空间上限,也会报oom。方法区内存溢出主要是由于动态加载类的数量太多,或者是不断创建一个动态代理,用不了多久方法区内存也会溢出,会报oom,这里在1.7之前会报permgem oom,1.8则会报meta space oom,这是因为1.8中删除了堆中的永久代,转而使用元数据区。内存泄漏一般是因为对象被引用无法回收,比如一个集合中存着很多对象,可能你在外部代码把对象的引用置空了,但是由于对象还被集合给引用着,所以无法被回收,导致内存泄漏。测试也很简单,就在集合里添加对象,添加完以后把引用置空,循环操作,一会就会出现oom异常,原因是内存泄漏太多了,导致没有空间分配新的对象。常见调试工具命令行工具有jstack jstat jmap 等,jstack可以跟踪线程的调用堆栈,以便追踪错误原因。jstat可以检查jvm的内存使用情况,gc情况以及线程状态等。jmap用于把堆栈快照转储到文件系统,然后可以用其他工具去排查。visualvm是一款很不错的gui调试工具,可以远程登录主机以便访问其jvm的状态并进行监控。class文件结构class文件结构比较复杂,首先jvm定义了一个class文件的规则,并且让jvm按照这个规则去验证与读取。开头是一串魔数,然后接下来会有各种不同长度的数据,通过class的规则去读取这些数据,jvm就可以识别其内容,最后将其加载到方法区。JVM的类加载机制jvm的类加载顺序是bootstrap类加载器,extclassloader加载器,最后是appclassloader用户加载器,分别加载的是jdk/bin ,jdk/ext以及用户定义的类目录下的类(一般通过ide指定),一般核心类都由bootstrap和ext加载器来加载,appclassloader用于加载自己写的类。双亲委派模型,加载一个类时,首先获取当前类加载器,先找到最高层的类加载器bootstrap让他尝试加载,他如果加载不了再让ext加载器去加载,如果他也加载不了再让appclassloader去加载。这样的话,确保一个类型只会被加载一次,并且以高层类加载器为准,防止某些类与核心类重复,产生错误。defineclass findclass和loadclass类加载classloader中有两个方法loadclass和findclass,loadclass遵从双亲委派模型,先调用父类加载的loadclass,如果父类和自己都无法加载该类,则会去调用findclass方法,而findclass默认实现为空,如果要自定义类加载方式,则可以重写findclass方法。常见使用defineclass的情况是从网络或者文件读取字节码,然后通过defineclass将其定义成一个类,并且返回一个Class对象,说明此时类已经加载到方法区了。当然1.8以前实现方法区的是永久代,1.8以后则是元空间了。JVM虚拟机字节码执行引擎jvm通过字节码执行引擎来执行class代码,他是一个栈式执行引擎。这部分内容比较高深,在这里就不献丑了。编译期优化和运行期优化编译期优化主要有几种1 泛型的擦除,使得泛型在编译时变成了实际类型,也叫伪泛型。2 自动拆箱装箱,foreach循环自动变成迭代器实现的for循环。3 条件编译,比如if(true)直接可得。运行期优化主要有几种1 JIT即时编译Java既是编译语言也是解释语言,因为需要编译代码生成字节码,而后通过解释器解释执行。但是,有些代码由于经常被使用而成为热点代码,每次都编译太过费时费力,干脆直接把他编译成本地代码,这种方式叫做JIT即时编译处理,所以这部分代码可以直接在本地运行而不需要通过jvm的执行引擎。2 公共表达式擦除,就是一个式子在后面如果没有被修改,在后面调用时就会被直接替换成数值。3 数组边界擦除,方法内联,比较偏,意义不大。4 逃逸分析,用于分析一个对象的作用范围,如果只局限在方法中被访问,则说明不会逃逸出方法,这样的话他就是线程安全的,不需要进行并发加锁。1JVM的垃圾回收1 GC算法:停止复制,存活对象少时适用,缺点是需要两倍空间。标记清除,存活对象多时适用,但是容易产生随便。标记整理,存活对象少时适用,需要移动对象较多。2 GC分区,一般GC发生在堆区,堆区可分为年轻代,老年代,以前有永久代,现在没有了。年轻代分为eden和survior,新对象分配在eden,当年轻代满时触发minor gc,存活对象移至survivor区,然后两个区互换,等待下一场gc,当对象存活的阈值达到设定值时进入老年代,大对象也会直接进入老年代。老年代空间较大,当老年代空间不足以存放年轻代过来的对象时,开始进行full gc。同时整理年轻代和老年代。一般年轻代使用停止复制,老年代使用标记清除。3 垃圾收集器serial串行parallel并行它们都有年轻代与老年代的不同实现。然后是scanvage收集器,注重吞吐量,可以自己设置,不过不注重延迟。cms垃圾收集器,注重延迟的缩短和控制,并且收集线程和系统线程可以并发。cms收集步骤主要是,初次标记gc root,然后停顿进行并发标记,而后处理改变后的标记,最后停顿进行并发清除。g1收集器和cms的收集方式类似,但是g1将堆内存划分成了大小相同的小块区域,并且将垃圾集中到一个区域,存活对象集中到另一个区域,然后进行收集,防止产生碎片,同时使分配方式更灵活,它还支持根据对象变化预测停顿时间,从而更好地帮用户解决延迟等问题。
-
描述Core 功能实现以 Dispatcher(CFE: Common Fault-Injection Entry)为故障注入统一入口,其作用是提供故障注入/清除/查询的执行程序,Dispatcher解析故障模式 相关的参数后,将参数信息传入到对应的故障模拟工具(如 CPU、Mem、File)并执行 调用,从而达到故障模拟的效果,逻辑架构图如下所示:计算芯片注入计算芯片注入主要利用ACPI(Advanced Configuration and Power Interface)中的报告硬件错误模块 APEI(Advanced Platform Error Interfaces)的EINJ(Error Injection Table)、Linux内核模块以及devmem 工具来让硬件与固件配合产生故障。 支持CPU、内存、PCIE、NPU等硬件故障注入能力。大致逻辑架构如下图所示:通过开发相关的脚本或者APP,对芯片中的某些地址进行映射,然后再调用上述的内核模块、工具程序完成相应的硬件模块的故障注入能力。Linux OS 注入Linux OS注入功能包含了多种模块,包含文件系统、磁盘、网络、进程、内存、系统管理等各个方面的系统资源故障模拟。因不同资源的故障模拟方式不同,目前主要使用了两种故障注入手段,分别是用户态与内核态,用户态故障注入只需要调用用户态的程序或功能即可完成模拟,而内核态故障注入则需要拦截OS内核的操作实现模拟。 CPU 过载:利用CPU调度函数sched_setaffinity将指定线程绑定至指定CPU核上,并启动死循环消耗CPU的利用率;当需要指定过载百分比时,对消耗进程进行CPU消耗限制,从而达到指定百分比;如果是正弦消耗,则可以根据正弦函数,计算CPU的空闲时间,从而达到正弦消耗的原理。内存过载:按照系统分页的最大值(4K),循环申请内存(malloc),直到达到指定的内存消耗大小,申请完成之后,进程不退出,这样内存一致被占用,从而构造内存过载(依此可以实现指定百分比的内存过载)。 网络包故障:网络类故障借助 Linux 系统的tc(Traffic Control)工具的netem模 块实现故障模拟,它利用队列规定建立处理数据包的队列,并定义队列中的数据包被 发送的方式,从而实现对流量的控制。而netem 队列也是无类队列,也就是说所有从 网卡发送出去的包都会受到配置参数的影响。进程故障:进程类故障多是利用Linux 下的gdb 调试工具,attach 至指定的进程 之中,再利用gdb来加载自定义的so库文件,从而实现死循环、线程数过多、内存泄露、句柄耗尽等故障。除加载so之外,还存在少部分故障模式需要通过内核来控制,在脚本内加载内核模块process_device.ko,主要是进程 D状态、Z状态等等。文件系统故障:文件系统的故障模式基本与内核相关,自定义实现相关内核模块,并在注入脚本或程序中加载该内核模块,从而实现对内核中相关功能进行拦截,这样当内核进行文件操作时,由自定义内核模块拦截,并篡改为相应的故障模式,比如:文件空、文件全零等等(存储链路相关的故障也与之相似)。除这些典型故障模式之外,还存在部分故障模式是由其他的脚本与工具实现的,目前集成的部分主要是依靠dd命令。Docker容器注入容器工具以统一程序为入口,并由该入口程序,调用对应的相关模块程序,对应的模块程序可以分为四类: 容器API调用:主要是容器实例相关的故障模式,通过官方提供的客户端,调用容器本身自带的一些API进行故障模拟。例如:容器实例重启,容器实例异常终止等。 容器内部执行:主要是消耗资源的故障模式,它们最好的执行方式就是在容器内去启动相应进程去消耗容器资源,因此这类故障需要先将资源消耗程序拷贝至容器之中,再启动程序,达到资源消耗的目的。例如:CPU过载、内存过载等。 容器外部(OS中)执行:主要是针对在容器中实现存在困难的一些故障模式,一些容器实例属于精简OS镜像,一些命令不会存在,这时就需要通过侵入容器的命名空间 (namespace)进行故障模拟。例如:网络类、文件系统类。 启动故障注入容器:利用容器的cgroups、namespace特性将目标容器的资源与故障注入容器进行共享,然后再在故障注入容器之中进行故障注入。 容器的故障注入程序正是基于上述的四种方法,覆盖了容器CPU、内存、实例、文件系统、进程、网络六大模块的故障注入能力。下一期我们一起来学习通过DemonCAT怎么实现计算全栈的故障注入,敬请期待~
上滑加载中
推荐直播
-
OpenHarmony应用开发之网络数据请求与数据解析
2025/01/16 周四 19:00-20:30
华为开发者布道师、南京师范大学泰州学院副教授,硕士研究生导师,开放原子教育银牌认证讲师
科技浪潮中,鸿蒙生态强势崛起,OpenHarmony开启智能终端无限可能。当下,其原生应用开发适配潜力巨大,终端设备已广泛融入生活各场景,从家居到办公、穿戴至车载。 现在,机会敲门!我们的直播聚焦OpenHarmony关键的网络数据请求与解析,抛开晦涩理论,用真实案例带你掌握数据访问接口,轻松应对复杂网络请求、精准解析Json与Xml数据。参与直播,为开发鸿蒙App夯实基础,抢占科技新高地,别错过!
回顾中 -
Ascend C高层API设计原理与实现系列
2025/01/17 周五 15:30-17:00
Ascend C 技术专家
以LayerNorm算子开发为例,讲解开箱即用的Ascend C高层API
回顾中
热门标签