首页 文章 精选 留言 我的

精选列表

搜索[AI编程],共10000篇文章
优秀的个人博客,低调大师

【Go专家编程】5分钟玩转go.sum

为了确保一致性构建,Go引入了go.mod文件来标记每个依赖包的版本,在构建过程中go命令会下载go.mod中的依赖包,下载的依赖包会缓存在本地,以便下次构建。 考虑到下载的依赖包有可能是被黑客恶意篡改的,以及缓存在本地的依赖包也有被篡改的可能,单单一个go.mod文件并不能保证一致性构建。 为了解决Go module的这一安全隐患,Go开发团队在引入go.mod的同时也引入了go.sum文件,用于记录每个依赖包的哈希值,在构建时,如果本地的依赖包hash值与go.sum文件中记录得不一致,则会拒绝构建。 go.sum文件记录 go.sum文件中每行记录由module名、版本和哈希组成,并由空格分开: <module> <version>[/go.mod] <hash> 比如,某个go.sum文件中记录了github.com/google/uuid 这个依赖包的v1.1.1版本的哈希值: github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 在Go module机制下,我们需要使用依赖包名称和版本才可以准确的描述一个依赖,为了方便叙述,下面我们使用依赖包版本来指代依赖包名称和版本。 正常情况下,每个依赖包版本会包含两条记录,第一条记录为该依赖包版本整体(所有文件)的哈希值,第二条记录仅表示该依赖包版本中go.mod文件的哈希值,如果该依赖包版本没有go.mod文件,则只有第一条记录。如上面的例子中,v1.1.1表示该依赖包版本整体,而v1.1.1/go.mod表示该依赖包版本中go.mod文件。 依赖包版本中任何一个文件(包括go.mod)改动,都会改变其整体哈希值,此处再额外记录依赖包版本的go.mod文件主要用于计算依赖树时不必下载完整的依赖包版本,只根据go.mod即可计算依赖树。 每条记录中的哈希值前均有一个表示哈希算法的h1:,表示后面的哈希值是由算法SHA-256计算出来的,自Go module从v1.11版本初次实验性引入,直至v1.14 ,只有这一个算法。 此外,细心的读者或许会发现go.sum文件中记录的依赖包版本数量往往比go.mod文件中要多,这是因为二者记录的粒度不同导致的。go.mod只需要记录直接依赖的依赖包版本,只在依赖包版本不包含go.mod文件时候才会记录间接依赖包版本,而go.sum则是要记录构建用到的所有依赖包版本。 生成 在GOMODULE模式下,当我们引入一个新的依赖(使用import xxx)时,在使用go命令(go build、go get等)时都会触发依赖包的下载,依赖包首先会被下载到本地的缓存目录$GOPATH/pkg/mod/cache/download,下载的依赖包为某个.zip文件,go命令在下载完后成会对该.zip文件计算哈希值,并保存在后缀为.ziphash的文件中,此哈希值即为依赖包版本整体的哈希值。为了更新go.sum,还会将该.zip包解压并取出go.sum再次计算哈希值,此哈希值即为依赖包版本中go.mod文件的哈希值。 简单来说,go.sum中的记录是go命令首次添加进去的。而且后续随着依赖的增加或减少,go命令也会相应的新增或删除go.sum中的记录。 校验 在GOMODULE模式下,当我们使用go命令构建项目时,如果项目中包含go.sum文件,则会进行依赖包版本的校验。go命令会跟据go.mod到本地缓存目录$GOPATH/pkg/mod/cache/download寻找相应依赖包版本的哈希值,然后与项目中go.sum记录的哈希值比较,如果不一致则表示校验失败,go命令将会拒绝执行。 校验失败说明本地缓存目录中依赖包版本的哈希值和项目中go.sum中记录的哈希值不一致,此时有意思的是如何区别谁对谁错。需要说明的是,二者都可能出错,本地缓存目录中的依赖包版本有可能被有意或无意地修改过,项目中go.sum中记录的哈希值也可能被篡改过。 为了解决这个问题,Go团队引入了GOSUMDB来充当仲裁的角色。 仲裁 环境变量GOSUMDB标识一个checksum database,即校验和数据库。该数据库中记录了很多依赖包版本的哈希值,当本地缓存目录中依赖包版本的哈希值和项目中go.sum中记录的哈希值不一致时,将会从GOSUMDB指示的数据库中查询得到一个准确的哈希值,据此识别出哪个是正确的。 如果是项目中的go.sum哈希值错误,go命令会自动更新go.sum,如果是缓存中的哈希值错误,则go命令拒绝向下执行,需要开发人员自行检查依赖包版本是否被修改过。 此外,环境变量GOSUMDB默认值为sum.golang.org,它是Google 官方维护的一个数据库,其会缓存所有可公开获得的依赖包,也可以指定自行搭建的数据库。还可以使用GOPRIVATE环境变量指定私有的依赖包,从而跳过校验。 参考文档 命令行帮助文档:$ go help module-auth Go 1.14 源码 赠人玫瑰手留余香,如果觉得不错请给个赞~ 本篇文章已归档到GitHub项目,求星~ 点我即达

优秀的个人博客,低调大师

图解Go里面的WaitGroup了解编程语言核心实现源码

1. 基础筑基 sync.WaitGroup里面的实现逻辑其实蛮简单的,在看过之前的sync.Mutex和synx.RWMutex之后,阅读起来应该非常简单,而唯一有差异的其实就是sync.WaitGroup里面的state1 1.1 等待机制 sync.WaitGroup主要用于等待一组goroutine退出,本质上其实就是一个计数器,我们可以通过Add指定我们需要等待退出的goroutine的数量,然后通过Done来递减,如果为0,则可以退出 1.2 内存对齐 内存对齐是一个比较大的话题,其核心机制是编译器根据结构体内部元素的size结合平台和编译器自身的规则来进行补位, 而在sync.WaitGroup里面就有用到,也是我感觉可能在WaitGroup所有实现的核心特性里面最重要的一条了 在WaitGroup里面只有state1 [3]uint32这一个元素,通过类型我们可以计算uint32是4个字节,长度3的数组总长度12,其实之前这个地方是[12]byte, 切换uint32是go语言里面为了让底层的编译器保证按照4个字节对齐而做的切换 1.3 8字节 8字节即两个4字节,也就是两个uint32的长度,实际上也是一个uint64的长度,在sync.WaitGroup里面通过uint64来进行等待数量的计数 这里有一个相对比较hack的问题,我翻阅过很多文章,都没有找到能让我完全信服的答案,接下来就是我自己的臆测了 1.4 8字节的臆测 首先go语言需要兼容32位和64位平台,但是在32位平台上对64字节的uint操作可能不是原子的,比如在读取一个字长度的时候,另外一个字的数据很有可能已经发生改变了(在32位操作系统上,字长是4,而uint64长度为8), 所以在实际计数的时候,其实sync.WaitGroup也就使用了4个字节来进行 在cpu内有一个cache line的缓存,这个缓存通常是8个字节的长度,在intel的cpu中,会保证针对一个cache line的操作是原子,如果只有8个字节很有可能会出现上面的这种情况,即垮了两个cache line, 这样不论是在原子操作还是性能上可能都会有问题 1.5 测试8字节指针 我这里简单构造了一个8字节的长度指针,来做演示,通过读取底层数组的指针和偏移指针(state1数组的第2个元素即index=1)的地址,可以验证猜想即在经过编译器进行内存分配对齐之后,如果当前元素的指针的地址不能为8整除,则其第地址+4的地址,可以被8整除(这里感觉更多的是在编译器层才能看到真正的东西,而我对编译器本身并不感兴趣,所以我只需要一个证明,可以验证结果即可) import ( "unsafe" ) type a struct { b byte } type w struct { state1 [3]uint32 } func main() { b := a{} println(unsafe.Sizeof(b), uintptr(unsafe.Pointer(&b)), uintptr(unsafe.Pointer(&b))%8 == 0) wg := w{} println(unsafe.Sizeof(wg), uintptr(unsafe.Pointer(&wg.state1)), uintptr(unsafe.Pointer(&wg.state1))%8 == 0) println(unsafe.Sizeof(wg), uintptr(unsafe.Pointer(&wg.state1[1])), uintptr(unsafe.Pointer(&wg.state1[1]))%8 == 0) } 输出结果 1 824633919343 false 12 824633919356 false 12 824633919360 true 1.6 分段计数 在sync.WaitGroup中对上面的提到的8字节的uint64也是分段计数,即高位记录需要等待 Done的数量,而低位记录当前正在Wait等待结束的计数 2. 源码速读 1.核心原理就是通过之前说的64位的uint64来进行计数,采用高位记录需要Done的数量,低位记录Wait的数量 2.如果发现当前count>0则Wait的goroutine会进行排队 3.任务完成后的goroutine则进行Done操作,直到count==0,则完成,就唤醒所有因为wait操作睡眠的goroutine 2.1 计数与信号量 就像基础部分说的那样,针对12字节的[3]uint32会根据当前指针的地址来进行计算,确定采用哪个分段进行计数和做为信号量等待,详细的说明上面已经提过,这里只是根据采取的分段,然后将对应的分段转换为*uint64的指针和一个uint32的指针就可以了 func (wg *WaitGroup) state() (statep *uint64, semap *uint32) { if uintptr(unsafe.Pointer(&wg.state1))%8 == 0 { return (*uint64)(unsafe.Pointer(&wg.state1)), &wg.state1[2] } else { return (*uint64)(unsafe.Pointer(&wg.state1[1])), &wg.state1[0] } } 2.2 添加等待计数 func (wg *WaitGroup) Add(delta int) { // 获取当前计数 statep, semap := wg.state() if race.Enabled { _ = *statep // trigger nil deref early if delta < 0 { // Synchronize decrements with Wait. race.ReleaseMerge(unsafe.Pointer(wg)) } race.Disable() defer race.Enable() } // 使用高32位进行counter计数 state := atomic.AddUint64(statep, uint64(delta)<<32) v := int32(state >> 32) // 获取当前需要等待done的数量 w := uint32(state) // 获取低32位即waiter等待计数 if race.Enabled && delta > 0 && v == int32(delta) { // The first increment must be synchronized with Wait. // Need to model this as a read, because there can be // several concurrent wg.counter transitions from 0. race.Read(unsafe.Pointer(semap)) } if v < 0 { panic("sync: negative WaitGroup counter") } if w != 0 && delta > 0 && v == int32(delta) { panic("sync: WaitGroup misuse: Add called concurrently with Wait") } // 如果当前v>0,则表示还需要继续未完成的goroutine进行Done操作 // 如果w ==0,则表示当前并没有goroutine在wait等待结束 // 以上两种情况直接返回即可 if v > 0 || w == 0 { return } // 当waiters > 0 的时候,并且当前v==0,这个时候如果检查发现state状态前后发生改变,则 // 证明当前有人修改过,则删除 // 如果走到这个地方则证明经过之前的操作后,当前的v==0,w!=0,就证明之前一轮的Done已经全部完成,现在需要唤醒所有在wait的goroutine // 此时如果发现当前的*statep值又发生了改变,则证明有有人进行了Add操作 // 也就是这里的WaitGroup滥用 if *statep != state { panic("sync: WaitGroup misuse: Add called concurrently with Wait") } // 将当前state的状态设置为0,就可以进行下次的重用了 *statep = 0 for ; w != 0; w-- { // 释放所有排队的waiter runtime_Semrelease(semap, false) } } 2.2 Done完成一个等待事件 func (wg *WaitGroup) Done() { // 减去一个-1 wg.Add(-1) } 2.3 等待所有操作完成 func (wg *WaitGroup) Wait() { statep, semap := wg.state() if race.Enabled { _ = *statep // trigger nil deref early race.Disable() } for { // 获取state的状态 state := atomic.LoadUint64(statep) v := int32(state >> 32) // 获取高32位的count w := uint32(state) // 获取当前正在Wait的数量 if v == 0 { // 如果当前v ==0就直接return, 表示当前不需要等待 // Counter is 0, no need to wait. if race.Enabled { race.Enable() race.Acquire(unsafe.Pointer(wg)) } return } // 进行低位的waiter计数统计 if atomic.CompareAndSwapUint64(statep, state, state+1) { if race.Enabled && w == 0 { // Wait must be synchronized with the first Add. // Need to model this is as a write to race with the read in Add. // As a consequence, can do the write only for the first waiter, // otherwise concurrent Waits will race with each other. race.Write(unsafe.Pointer(semap)) } // 如果成功则进行排队休眠等待唤醒 runtime_Semacquire(semap) // 如果唤醒后发现state的状态不为0,则证明在唤醒的过程中WaitGroup又被重用,则panic if *statep != 0 { panic("sync: WaitGroup is reused before previous Wait has returned") } if race.Enabled { race.Enable() race.Acquire(unsafe.Pointer(wg)) } return } } } 参考文章 关于cpu cache line大小 原子操作 关注公告号阅读更多源码分析文章 更多文章关注 www.sreguide.com 本篇文章由一文多发平台ArtiPub自动发布

优秀的个人博客,低调大师

《Python编程从0到1》笔记3——欧几里得算法

本节以欧几里得算法(这是人类历史上最早记载的算法)为示例,向读者展示注释、文档字符串(docstring)、变量、循环、递归、缩进以及函数定义等Python语法要素。 欧几里得算法:“在数学中,辗转相除法,又称欧几里得算法(Euclidean algorithm),是求最大公约数的算法。辗转相除法首次出现于欧几里得的《几何原本》(第VII卷,命题i和ii)中,而在中国则可以追溯至东汉出现的《九章算术》。两个整数的最大公约数是能够同时整除它们的最大的正整数。辗转相除法基于如下原理:两个整数的最大公约数等于其中较小的数和两数的差的最大公约数。”---《维基百科.辗转相除法》 在实际操作中,可以使用带余数除法替代减法以减少步骤。下面是使用流程图绘制的算法示意图:图 1.2 欧几里得算法流程图 在程序设计实践中,很少针对简单的程序绘制流程图。因为对于熟练的程序设计者来说,代码本身足以清晰地说明程序的执行流程。流程图往往用于描述大的软件系统的工作原理,或者用来辅助不够结构化的语言(如汇编语言)。 根据前述算法描述,计算252和105的最大公约数的计算步骤如下:1.252除以105余42,问题转为求105和42的最大公约数;2.105除以42余21,问题转为求42和21的最大公约数;3.42除以21可以除尽,达到算法终点;4.结论:252和105的最大公约数为21。 代码 1.2展示了欧几里得算法的Python实现: #!/usr/bin/env python3 def gcd(a, b): while b!=0: a, b = b, a%b return a print(gcd(252, 105)) 代码 1.2的核心部分定义了用来求最大公约数的函数gcd,为了便于说明,将这部分提取如图 1.3所示:图 1.3 gcd 函数图示 代码说明: 第1行定义了有两个参数的函数gcd()。函数是一段可以被反复调用的代码。gcd()函数计算参数a和b的最大公约数,并通过第4行的return语句返回计算的结果; 第2行while语句,请读者注意到这行连同在排版上用4个空格缩进,这表示这条语句属于gcd()函数(没有缩进的最后一行print()语句就不属于gcd函数)。while关键字后面跟随的条件判断"b!=0"表示当这个条件为真时就反复执行之后的第3行语句; 第2行语句是赋值语句,将b的值和a除以b的余数,再次赋值给a和b。这行每执行一次,就完成了一次“辗转相除”。这行语句前有8个空格,表明这行语句受前一条while语句控制,直至while之后的"b!=0"条件不为真,才停止执行。换言之就是当某次余数为0时停止执行。这实际上就是上面描述的欧几里得算法; 第4行语句是返回语句,将最后剩下的公约数a返回; 最后使用print语句将gcd(252, 105)的返回值打印出来。 程序运行结果: $ ./gcd.py 21 可以使用python3解释器的 -i 命令行选项在启动解释器交互界面时加载执行程序文本。加载执行程序文本后,可以继续键入代码以执行: $ python3 -i gcd.py 21 >>> gcd(12, 4) 4 >>> gcd(36, 54) 18 这是一本很有趣很有趣的Python入门书,墙裂推荐。

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册