2024-05-06 17:25:54
我们有这么一段业务代码,在 Gin 的 API Handler 中,开了一个子 goroutine 写 DB,代码大概是这样:
package main
import (
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
var db *gorm.DB
func ServerHandler(c *gin.Context) {
// 一些旁路逻辑,为了不影响接口耗时,在子goroutine中执行
go func() {
db.WithContext(c).Exec("update xxx")
}()
// 一些后置逻辑
}
代码在测试阶段一直没啥问题,但是一上线立马出现了大面积的 panic。panic 的栈也非常奇怪,挂在了 mysql driver 里面:
panic: sync/atomic: store of nil value into Value
goroutine 357413 [running]:
sync/atomic.(*Value).Store(0xc004097ef0, {0x0,0x0})
/usr/local/go/src/sync/atomic/value.go:47 +0xeb
github.com/go-sql-driver/mysql.(*atomicError).Set(..)
/root/go/pkg/mod/github.com/go-sql-driver/[email protected]/utils.go:831
github.com/go-sql-driver/mysql.(*mysqlConn).cancel(0xc004e6fc20, {0x0, 0x0})
/root/go/pkg/mod/github.com/go-sql-driver/[email protected]/connection.go:435 +0x3d
github.com/go-sql-driver/mysql.(*mysqlConn).startWatcher.func1()
/root/go/pkg/mod/github.com/go-sql-driver/[email protected]/connection.go:622 +0x192
created by github.com/go-sql-driver/mysql.(*mysqlConn).startWatcher
/root/go/pkg/mod/github.com/go-sql-driver/[email protected]/connection.go:611 +0x105
2023-11-29 13:05:01
几乎世界上每个 Golang 程序员都踩过一遍 for 循环变量的坑,而这个坑的解决方案已经作为实验特性加入到了 Go 1.21 中,并且有望在 Go 1.22 中完全开放。
举个例子,有这么段代码:
var ids []*int
for i := 0; i < 10; i++ {
ids = append(ids, &i)
}
for _, item := range ids {
println(*item)
}
可以试着在 playgound 里面运行下:go.dev/play/p/O8MVGtueGAf
答案是:打印出来的全是 10。
这个结果实在离谱。原因是因为在目前 Go 的设计中,for 中循环变量的定义是 per loop 而非 per iteration。也就是整个 for 循环期间,变量 i
只会有一个。以上代码等价于:
var ids []*int
var i int
for i = 0; i < 10; i++ {
ids = append(ids, &i)
}
同样的问题在闭包使用循环变量时也存在,代码如下:
2023-11-25 19:25:54
Bigcache是用Golang实现的本地内存缓存的开源库,主打的就是可缓存数据量大,查询速度快。 在其官方的介绍文章《Writing a very fast cache service with millions of entries in Go》一文中,明确提出了bigcache的设计目标:
目前有许多开源的cache库,大部分都是基于map实现的,例如go-cache,ttl-cache等。bigcache明确指出,当数据量巨大时,直接基于map实现的cache库将出现严重的性能问题,这也是他们设计了一个全新的cache库的原因。
本文将通过分析bigcache v3.1.0的源码,揭秘bigcache如何解决现有map库的性能缺陷,以极致的性能优化,实现超高性能的缓存库。
2023-11-23 12:10:19
最近发现 Golang 标准库竟然自带了 varint 的实现,代码位置在 encoding/binary/varint.go,这个跟protobuf里面的varint实现基本是一致的。刚好借助 golang 标准库的 varint 源码,我们来系统地学习和梳理下 varint。
熟悉 protobuf 的人肯定对 varint 不陌生,protobuf 里面除了带 fix (如 fixed32、fixed64) 之外的整数类型, 都是 varint 编码。
varint 的出现主要是为了解决两个问题:
本文将通过分析 Golang 标准库自带的 varint 源码实现,介绍 varint 的设计原理以及Golang标准库是如何解决 varint 在编码负数时遇到的问题。
2021-07-18 11:30:19
sync.Pool 是 Golang 内置的对象池技术,可用于缓存临时对象,以缓解因频繁建立临时对象带来的性能损耗以及对 GC 带来的压力。
在许多知名的开源库中都可以看到 sync.Pool 的大量使用。例如,HTTP 框架 Gin 用 sync.Pool 来复用每个请求都会创建的 gin.Context
对象。 在 grpc-Go、kubernetes 等也都可以看到对 sync.Pool 的身影。
但需要注意的是,sync.Pool 缓存的对象随时可能被无通知的清除,因此不能将 sync.Pool 用于存储持久对象的场景。
sync.Pool 作为 goroutine 内置的官方库,其设计非常精妙。sync.Pool 不仅是并发安全的,而且实现了 lock free,里面有许多值得学习的知识点。
本文将基于 go-1.16 的源码 对 sync.Pool 的底层实现一探究竟。
2021-06-20 13:30:19
问题是这样的:我在代码里面调用了 os.Chmod("test.txt", 777)
,希望把该文件的读写及执行权限对所有用户开放。
执行完代码,顺手 ls 看了下。如下:
$ ls -l test.txt
-r----x--x 1 cyhone 1085706827 0 Jun 20 13:27 test.txt
结果出乎意料,不仅文件权限没有按预期的变成 rwxrwxrwx
。反而执行完后,当前用户就只剩可读权限了,其他用户就只有可执行权限同时无读写权限。
因为这实在是一个简单又愚蠢的错误,所以先直接给出结论:
rwxrwxrwx
,需要写成 0777
,而非 777
。chmod
命令的表示形式,用八进制表示更方便和准确点。0777
和 777
都可以。这个问题虽然非常简单,但尴尬的是我还踩了坑,所以把这个问题及原因分享出来。