md5 128位但长度为32位

背景: 后端(Golang) 接口卡控前端给的md5 值的长度, 百度md5 结构为128位长度,心理默认8位为一个字符。卡控判断md5 长度为16位,但是前端时间传入 md5 长度位32字符 原因: golang 的 len 判断的是 字符数(bytes) md5 的结果是128位, 也即是16bytes 没错,但是通常md5会原先转成16进制,即4位一个字符,即转化成一个128/4=32 字符的字符串。传递到后端即为32位字符。

<span title='2024-03-05 20:27:39 +0800 +0800'>March 5, 2024</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

Go Build Time Variable

使用背景 一套程序代码,配置文件,需要同时在不同的云厂商运行。 通过修改 jenkins, 的部署 pipeline , 让开发人员准确的获取到当前程序的运行环境 还能保持代码统一. ldflags 修改的变量, 无大小写限制, 小写变量也可修改编译过程进行修改 变量引用需要 fullpath. 例如 main 文件中的变量 go build -ldflags="-X 'main/varName=xxx' 子包,需要当前 mod 包名称开头。 可以查看第二篇文章 go build variable https://programmingpercy.tech/blog/modify-variables-during-build/ 发现更加简单的解决方案 go build -tags targetCloud https://pkg.go.dev/go/build#hdr-Build_Constraints

<span title='2023-11-20 13:50:52 +0800 +0800'>November 20, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

GO 代码结构中为什么老有 cmd 和 internal 文件夹

总结: cmd 一个代码库多个启动程序, 清晰明了 internal 将外部依赖放置在 internal 外面, 避免内部逻辑代码直接引用. 比如, 数据持久层 MySQL 参考一下 ChatGPT 给我的答案: 在 Go 语言中,使用 cmd 目录和 internal 目录的组合是一种常见的方式来组织项目结构。 cmd 目录通常包含可执行程序的入口点。这些可执行程序可以是应用程序的命令行接口、工具或测试二进制文件。每个子目录都包含一个可执行程序的主要文件,以及与其关联的其他文件。这种风格使得每个程序都可以独立编译和分发,并且可以让其它人轻松了解和使用它们。 对于 internal 目录,其命名约束了该目录下的代码仅供内部使用。这意味着代码不应该由任何可以被外部依赖使用的包引用。这种风格允许对代码的控制更精细,因为程序员可以更好地控制哪些代码可以被其他人使用。 使用这样的结构来组织 Go 项目有很多好处,包括: 代码更易于维护和理解:使用独立的可执行程序和内部目录,可以让代码更清晰且更容易维护,使开发人员能够更好地理解代码的用途和位置。 扩展性强:该结构使得每个可执行程序都可以相对独立地进行开发、测试和部署。这也使得代码更有扩展和修改的完整性。 松耦合:使用 cmd 和 internal 目录方式可以大大增强代码间的松耦合度。这使得程序员可以更好地对代码变化做出反应,同时减少代码冲突及协调问题。 综上所述,使用 cmd 和 internal 目录方式更易于开发和维护,是现代化 Go 应用程序中广受欢迎的项目结构设计方式。

<span title='2023-08-03 20:34:58 +0800 +0800'>August 3, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

Golang 100 mistake 的简单读书笔记

variable shadowing init 弊端 初始化 error 的处理, 不应该是包自己用 panic 捕获错误终止程序,而是调用来决定是否应该终止程序 影响测试, 会在测试之前执行 获取执行数据,需要通过全局变量( 全局变量弊端:1. 会被修改,2. 影响测试) 什么时候使用 避免错误产生没有err 不会产生全局变量 getters and setters 如果只是简单获取值的话,就不应该用 方法命名 获取值 Balance 而不是 ( GetBalance) 设置值 SetBalance interface pollution interface 的价值 通用的方法( 如排序) 解耦 限制实体行为(仅提供特定方法) 只要在当我们真正用到的时候才去创建 we should create an interface when we need it, not when we foresee that we could need it interface 抽象 是通过发现的,而不是通过创造的 生产端接口声明,与实现放在同一个包中 消费端接口声明,与使用interface的放在同一个包中 Interface 是通过发现的,符合使用者的需求(与其他语言最大区别就是 Interface 为隐式实现)所以大部分接口应该是是消费端接口声明。 准备库定义生产端声明,可以提供自定义接口的能力。 生产端接口要尽量简洁。 return interfaceBe conservative in what you do, be liberal in fr Be conservative in what you do, be liberal in what you accept from others. ...

<span title='2023-05-25 12:10:53 +0800 +0800'>May 25, 2023</span>&nbsp;·&nbsp;10 min&nbsp;·&nbsp;潜水员

Golang 100 mistake 的简单读书笔记

variable shadowing init 弊端 初始化 error 的处理, 不应该是包自己用 panic 捕获错误终止程序,而是调用来决定是否应该终止程序 影响测试, 会在测试之前执行 获取执行数据,需要通过全局变量( 全局变量弊端:1. 会被修改,2. 影响测试) 什么时候使用 避免错误产生没有err 不会产生全局变量 getters and setters 如果只是简单获取值的话,就不应该用 方法命名 获取值 Balance 而不是 ( GetBalance) 设置值 SetBalance interface pollution interface 的价值 通用的方法( 如排序) 解耦 限制实体行为(仅提供特定方法) 只要在当我们真正用到的时候才去创建 we should create an interface when we need it, not when we foresee that we could need it interface 抽象 是通过发现的,而不是通过创造的 生产端接口声明,与实现放在同一个包中 消费端接口声明,与使用interface的放在同一个包中 Interface 是通过发现的,符合使用者的需求(与其他语言最大区别就是 Interface 为隐式实现)所以大部分接口应该是是消费端接口声明。 准备库定义生产端声明,可以提供自定义接口的能力。 生产端接口要尽量简洁。 return interfaceBe conservative in what you do, be liberal in fr Be conservative in what you do, be liberal in what you accept from others. ...

<span title='2023-05-25 12:10:53 +0800 +0800'>May 25, 2023</span>&nbsp;·&nbsp;10 min&nbsp;·&nbsp;潜水员

Golang Gin

Golang Gin 框架的使用和理解 框架代码地址 中间的几个关键对象 Engine RouterGroup HandlerFunc Context Engine gin 的框架实例, 包含路由地址, 中间件, 框架配置. 通过New()或者Default()创建 New 不带任何中间件 Default 会带 logger 和 recover 通过 Engine.Use() 添加中间件到Engine的RouterGroup Engine.Run 启动, 并绑定到参数的地址 RouterGroup 1 2 3 4 5 6 type RouterGroup struct { Handlers HandlersChain basePath string engine *Engine root bool } 用于存储中间件的处理方法 – 存储再 HandlersChain 里面, 即[]HandlerFunc 真正的路由地址存储在engine.trees – 路由树, 检索请求地址和对应处理方法. Engine 包含 RouterGroup, RouterGroup 也会存储 Engine 的地址. 目的是为了, 加点api地址时, 根据当前具体配置的中间件, 再添加engine的路由树. 也就是说, 过程中修改了中间件, 不会影响到已经配置的路由的中间件. ...

<span title='2023-01-01 22:08:25 +0800 +0800'>January 1, 2023</span>&nbsp;·&nbsp;4 min&nbsp;·&nbsp;潜水员

Golang 是否需要为每个请求 New 一个 Client

背景 在改动旧代码的时候把, 一个使用全局 http.Client 的代码弄成了每一个请求会新 New 一个 http.Client 导致下游的 nginx 的连接数暴涨. 问题 处理多个请求的时候, 是否需要为每个请求 New 一个 Client 探索 在 StackOverflow 发现的相关答案 How to release http.Client in Go? 给的答案是建议复用 Client The Client’s Transport typically has internal state (cached TCP connections), so Clients should be reused instead of created as needed. Clients are safe for concurrent use by multiple goroutines. http.Client 的结构体 1 2 3 4 5 6 7 8 9 10 type Client struct { Transport RoundTripper CheckRedirect func(req *Request, via []*Request) error Jar CookieJar Timeout time.Duration } 在 RoundTripper 中实现了连接复用的逻辑 ...

<span title='2022-12-19 15:35:53 +0800 +0800'>December 19, 2022</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;潜水员

代码整洁架构

代码整洁架构 核心思想 最重要的是依赖顺序需要内收 – 业务逻辑不能依赖框架 分层 简单分层四层 Entities Use Cases Interface Adapters Framework and Drivers Entity 实体抽象层 我的理解应该是在公司业务, 或者项目领域上对于业务模型的抽象. 不容易改变 (除非公司 业务, 或者项目方向改变). 应该是与 领域驱动设计 不谋而合 Use Cases 使用场景层 业务使用场景, 应该是存放相关不同业务场景的具体实现流程 Interface Adapters 接口转化器层 负责 Use Cases 数据 与外部使用数据转换器实现. 比较特别的例子, 将 Use Cases 产生的数据与外部的 UI 表现层所需要的数据格式做转化. Framework and Divers 数据库和框架层, 外部工具包接口依赖之类的. 依赖倒置 当遇到跨层依赖的时候, 内层需要引用到外层逻辑时: 比如, Use Case 要呈现 UI 数据结构 (Interface Adapters 层) 时, 不能直接引用 UI 层的数据模型. 而是, 通过依赖倒置. 将 UI 层的处理逻辑, 注入 Use Case 层进行处理, 实现目标. ...

<span title='2022-12-05 09:21:02 +0800 +0800'>December 5, 2022</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

go 使用 runtime 包进行内存占用分析

使用场景 写个demo, 想查看一下程序内部的内存占用情况. 使用方法 主角 runtime 包 对象 MemStats 方法 ReadMemStats demo 展示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // PrintMemUsage outputs the current, total and OS memory being used. As well as the number // of garage collection cycles completed. func PrintMemUsage() { bToMb := func(b uint64) uint64 { return b / 1024 / 1024 } var m runtime.MemStats runtime.ReadMemStats(&m) // For info on each, see: https://golang.org/pkg/runtime/#MemStats fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc)) fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc)) fmt.Printf("\tSys = %v MiB", bToMb(m.Sys)) fmt.Printf("\tNumGC = %v\n", m.NumGC) } 代码出处 ...

<span title='2022-11-18 19:27:35 +0800 +0800'>November 18, 2022</span>&nbsp;·&nbsp;5 min&nbsp;·&nbsp;潜水员

Golang Minimal Version Selection 意识流翻译(仅开头)

原文: Minimal Version Selection 意识流翻译, 只是简单的把自己的理解转化成中文. 仅供参考. 翻译: Go 构建相关的命令需要决定模块版本. 把这些模块和版本号称之为编译列表(build list). 为了稳定的开发, 今天的编译列表必须和明天的编译列表是一致的. 但是开发者也必须支持 编译列表的改变: 升级所有的模块, 升级单个模块, 降级单个模块. 版本选择的问题定位为: 给定一个算法支持对编译列表进行下面四种操作: 构建当前的编译列表 升级所有模块到各自对应的最新版本 升级一个模块到对应的版本 降级一个模块到对应的版本 最后两种操作是在一个具体的模块进行升级和降级. 在满足依赖需求的前提下, 尽量少的进 行升级, 降级, 添加或者移除模块. 这篇博文展示了最小版本选择算法, 一个新的, 简单的版本选择方案. 最小版本选择是容易 理解和能简单推测出他的运行行为的. 而且还能高稳定构建的, 表名一个用户所使用的依赖 是非常接近开发者所使用的依赖. 能够高效实现, 没有使用什么复杂的递归图算法. 仅仅只 用了几行 go 的代码就是实现了. 最小选择算法假设所有的模块都声明了它自己对于其他模块的依赖清单. 且所有的模块都遵 守语义化版本: 向前兼容的版本使用小版本号, 不向前兼容的版本使用大版本号. 对应的四种列表操作: 构建当前模块的编译列表: 将当前模块引用的依赖列表加在一起, 重复的仅保持一份. 升级所有模块版本: 所有最新引用的依赖, 重新构建一份列表 升级某一具体模块版本: 构建没有升级的列表, 再将升级的模块新的引用列表加入, 加入 时如果如果已经存在, 就是仅保存最新的版本 降级一个模块到对应的版本: 反解开所有依赖降级的模块, 每一个依赖都需降级到目标 模块版本之下 这些操作简单, 高效, 且容易实现 剩余内容 略 ...

<span title='2022-11-01 00:15:50 +0800 +0800'>November 1, 2022</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

Golang 解决依赖包版本冲突

遇到了 grpc 不遵循语义版本, 导致不同版本包之间的冲突. 更新了目标的版本模块之后, 编译一下就发现原先项目引用的 gozero 框架报错了. 搜索一下 相关的关键词,就可以定位到问题是 grpc 搞的鬼. 再找到对应的兼容版本, 升级到对应的版本就可以了. go 依赖版本选择 [golang 的最小版本选择]https://ynikl.github.io/blog/golang-minimal-version-selection/ 大体意思: 会选择当前编译需要依赖包的最高版本(使用语义化版本) 寻找依赖的原因 go mod why 寻找自己项目引用某个包的 最短引用路径, 导致会引用目标包的 1 go mod why google.golang.org/grpc 输出目标包的引用依赖层级 1 2 3 4 5 6 ❯ go mod why google.golang.org/grpc # google.golang.org/grpc hello/world/test git.test.cn/company-open/rpc-pkgs google.golang.org/grpc go mod graph 可以打印出, 模块的依赖图 1 2 3 4 5 6 example.com/main example.com/a@v1.1.0 example.com/main example.com/b@v1.2.0 example.com/a@v1.1.0 example.com/b@v1.1.1 example.com/a@v1.1.0 example.com/c@v1.3.0 example.com/b@v1.1.0 example.com/c@v1.1.0 example.com/b@v1.2.0 example.com/c@v1.2.0

<span title='2022-10-31 08:26:33 +0800 +0800'>October 31, 2022</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

Go errgroup 的基本用法

实现并发控制 在 golang 代码中如果要对一段代码进行并发限制. 通常的做法都是在写一个 channel 进行传入和传出. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func main() { concurrencyNum := 10 limitCh := make(chan bool, concurrencyNum) wg := new(sync.WaitGroup) for i := 0; i < 100; i++ { limitCh <- true wg.Add(1) go func() { defer func() { <-limitCh wg.Done() }() time.Sleep(1 * time.Second) fmt.Println("do some things...") }() } wg.Wait() fmt.Println("ok") } 如果如果中间运行代码有可能存在错误, 捕获错误. 有两种方法: ...

<span title='2022-09-19 09:19:31 +0800 +0800'>September 19, 2022</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;潜水员

如何查看 golang 编译之后调用的源码方法

在 golang 中查看源码是比较方便的. 可以直接到 官方包文档中直接查看文档和跳转到源码 但是, 当我们想看一些更加底层的实现方法时, 就需要知道编译器将对应的方法编译成 什么底层方法了. 比如, 我知道一些make(map[int]bool)是怎么实现的. 这时候就需要一些方法了. 引用一下鸟窝大佬的文章 总结一下三种方法: go tool compile -N -l -S makemap.go go tool compile -N -l -S makemap.go go tool compile -N -l -S makemap.go go tool compile 产生的汇编代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0x001c 00028 (main.go:5) FUNCDATA $1, gclocals·PdpXzE88ETLbqQ9okAZ04w==(SB) 0x001c 00028 (main.go:5) FUNCDATA $2, main.main.stkobj(SB) 0x001c 00028 (main.go:6) STP (ZR, ZR), main..autotmp_4-48(SP) 0x0020 00032 (main.go:6) STP (ZR, ZR), main..autotmp_4-32(SP) 0x0024 00036 (main.go:6) STP (ZR, ZR), main..autotmp_4-16(SP) 0x0028 00040 (main.go:6) MOVD $type.map[int]bool(SB), R0 0x0030 00048 (main.go:6) MOVD $100, R1 0x0034 00052 (main.go:6) MOVD $main..autotmp_4-48(SP), R2 0x0038 00056 (main.go:6) PCDATA $1, ZR 0x0038 00056 (main.go:6) CALL runtime.makemap(SB) 0x003c 00060 (main.go:6) MOVD R0, main.mp-112(SP) 0x0040 00064 (main.go:7) MOVD R0, R1 0x0044 00068 (main.go:7) MOVD ZR, R2 0x0048 00072 (main.go:7) MOVD $type.map[int]bool(SB), R0 go tool objdump产生的汇编代码 ...

<span title='2022-09-18 21:37:55 +0800 +0800'>September 18, 2022</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;潜水员

Content Disposition

Content-Disposition 常见是用在 http 请求的 Response 的 Header 头部. 告诉请求客户端(浏览器) 如何处理内容; Content-Disposition是在 MIME 标准定义的. http 中的用法只是其中的一小部分. 语法参数 inline 会在浏览器内部显示 1 Content-Disposition: inline attachment 会被保存成文件 1 Content-Disposition: attachment 后面可以跟 filename, 值为预设文件名称, 中间使用;分号隔开. 1 Content-Disposition: attachment; filename="filename.jpg" 拓展参数, 有两个文件名称参数可选 filename* filename filename* 采用了 RFC 5987 中规定的编码方式, 假如两个参数都使用 filename* 的优先级更高 RFC 5987 该提议最终还是引用 RFC 2231 中的编码方式. 下面简单介绍一下语法 *星号用于标记该同名参数是支持该编码语法的, 就如( filename* 之于 filename) '单个逗号用于分割 字符集名称 , 语言, 文件名称 %百分号用于标记编码方式, 参考RFC 2047, 所以文件名中不能有% 1 filename*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A 使用us-ascii编码, en-us英语 ...

<span title='2022-09-04 21:32:24 +0800 +0800'>September 4, 2022</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

Golang Map 介绍

本想写一篇关于 golang 中 map 底层的文章, 但是发现已经了相当不错的文章 – 字节跳动技术团队 - Golang 中 map 探究 这里只补充一下,缺少的 map 的删除操作 内部数据结构 初始化 map 是一个有"包含内容"的数据结构, 使用之前需要提前初始化, 即调用make 真正是调用源码是 runtime.makemap 获取数据 删除 源码地址 删除的关键代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // Only clear key if there are pointers in it. // # 当 Key 是指针类型的时候会去清空指针 if t.key.ptrdata != 0 { if goarch.PtrSize == 8 { *(*unsafe.Pointer)(k) = nil } else { // There are three ways to squeeze at one ore more 32 bit pointers into 64 bits. // Just call memclrHasPointers instead of trying to handle all cases here. memclrHasPointers(k, 8) } } e := add(unsafe.Pointer(b), dataOffset+bucketCnt*8+i*uintptr(t.elemsize)) // # 当 Value 为指针类型的时候, 指针为空, 解除引用 -> GC if t.elem.ptrdata != 0 { memclrHasPointers(e, t.elem.size) } else { memclrNoHeapPointers(e, t.elem.size) } // # 讲 hash 值标记为空 b.tophash[i] = emptyOne 上述删除代码操作现象 ...

<span title='2022-08-13 14:14:30 +0800 +0800'>August 13, 2022</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

xorm 的 session 和 salve 的区别

简单分析下xorm 里面 session 和 slave 里面 close 的代码 1 xorm.EngineGroup.Slave() Slave () 会直接返回 xorm.Engine对象(指代我们项目到数据库的逻辑连接(里面有tcp复用)) 如果这时候再调用 close 的会,会直接把 xorm.Engine 关闭(即数据库连接关闭)。 如果用这个 engine, 执行查询动作如 find, get时. 内部会有自动执行 NewSsession 和 Close, 所以不在需要手动调用close 1 xorm.EngineGroup.NewSsession() 会返回一个xorm.Session对象, 对应我们数据库操作中的"会话事务", 是否自动提交之类选项.(未手动开始事务时,都是自动提交) 调用 close, 对清除 session 中未提交的事务和一些缓存的 sql 前置处理语句 对于普通 select 语句调不调用是没什么太大影响 (但是还是建议new之后调用close)

<span title='2022-07-22 19:08:05 +0800 +0800'>July 22, 2022</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

Go中slice[i:j:k]第三个参数是做什么的

今天, 突然被同事卷到了, 被问到 golang 中 slice 的三个参数是干嘛的? 我突然一时间忘记了, golang 的重切片居然是可以接受第三个参数的, 枉费我已经了写了快两年的 go 了. 赶紧 google 一下, 并总结备忘. 简单介绍 slice 的数据结构 首先, 介绍一下 golang 中切片的结构体: 1 2 3 4 5 type slice struct { array unsafe.Pointer len int cap int } 有三个字段: array 是切片所指向的底层数组数据 len 就是切片的长度 cap 即容量, 很明显 源码地址 简单版重切片 a[low:high] 接受切片中的开始下标和结束下标, “左闭右开原则” 即重的切片数据会包含 low 下标的值, 但没有 high 下标的值 新切片的 容量(cap) 即为开始下标到原 slice 数据容量结束, 即"cap(a) - low" low 参数可以省略, 默认从 0 下标开始 high 参数也可以省略, 默认就是 slice 的长度, 即 len 的 value 1 2 3 4 5 6 7 8 a := [10]int{} oldSlice := a[:5] newSlice := a[2:4] fmt.Printf("b: len %d, cap %d, c: len %d, c:cap %d", len(b), cap(b), len(c), cap(c)) 输出: b: len 5, cap 10, c: len 2, c:cap 8 底层分配情况如下: ...

<span title='2022-06-09 23:52:30 +0800 +0800'>June 9, 2022</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;潜水员

小窥 Go 中的 Modules

是什么 Modules (下文称为模块)是 Go 语言设计用来管理依赖包。 在中文中我们经常把go中的 Module 和 Package 统称为“包”,其实 Module 是一个版本控制的“包”的集合。 模块的引入主要用于解决,项目依赖的不同版本的问题。(在旧的 GOPATH 中不同项目依赖不同版本)。引入的模块会声明在go.mod文件中。 go.mod 文件 1 2 3 4 5 6 7 8 9 module example.com/my/thing go 1.12 require example.com/other/thing v1.0.2 require example.com/new/thing/v2 v2.3.4 exclude example.com/old/thing v1.2.3 replace example.com/bad/thing v1.4.5 => example.com/good/thing v1.4.5 retract [v1.9.0, v1.9.5] 文件中各行意思 module 当前自己模块的名的完整路径 go 版本号 require 需要依赖的目标模块以及最小的版本号 exclude 排除目标模块的特定版本 replace 将某个模块版本指向另一个模块版本,也可以指向本地,未发布的模块 retract 当前发布的模块,需要撤回不能用版本,用于处理意外发布版本的情况 go.mod 可以通过相关命令修改,也可以直接修改文件数据 go.sum 文件 与go.mod经常在一起的会是一个go.sum文件,文件中存放着第一次添加依赖模块时,的模块源码的 hash 值。主要是用于校验,防止依赖模块代码被意外地修改到。 ...

<span title='2022-06-03 18:16:55 +0800 +0800'>June 3, 2022</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;潜水员

并发与并行的区别

举个例子,电脑的鼠标,键盘或者其他设备的驱动程序,他们是并发的,但不是并行的。他们也不需要并行去运行。 并发是很多程序(形容运行任务,不是广义上的程序)的独立运行,并发是一种程序设计结构 并行是一次性运行很多程序,并行是一种程序的运行现象,当成程序设计成并发的,他很容易就变成了并行, 但并行不一定是并发的目的。 Concurrency is not Parallelism by Rob Pike

<span title='2022-05-31 22:36:34 +0800 +0800'>May 31, 2022</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

Go-互斥锁的实现

Mutex 数据结构 1 2 3 4 type Mutex struct { state int32 sema uint32 } Mutex 使用过之后是不可被拷贝的 state 等于 0 值的时候才是无锁的状态 sema 字段为信号量字段,通过该字段控制协程的阻塞和唤醒,具体实现在runtime 中。 Mutex 对象总共有三个公开方法 Lock 尝试抢占互斥锁,如果已经被锁定,则调用协程进入阻塞 TryLock Unlock 解除互斥锁, 解锁未锁定的互斥锁会发生panic Mutex 与协程无法关,允许一个协程锁定,另一个协程进行解锁。 Mutex 实现了一个 sync.Locker 接口, 该接口只有两个方法 Lock Unlock Mutex 锁有几种状态 mutexLocked = 1 已经锁定 mutexWoken = 2 表示当前锁的等待队列,有协程正在活跃地获取锁,可以考虑不用释放信号量 mutexStarving = 4 当前锁已经进入了饥饿状态 其他常量 mutexWaiterShift = 3 统计的等待在Mutex.state字段等待数量。(前3位,用于表示锁的状态, 即 mutexLocked, mutexWoken, mutexStarving) starvationThresholdNs = 1e6 进入饥饿模式的阈值 1ms Mutex 锁的竞争方式 Mutex 锁有两种状态 ...

<span title='2022-05-29 20:52:35 +0800 +0800'>May 29, 2022</span>&nbsp;·&nbsp;4 min&nbsp;·&nbsp;潜水员