-
我给一个带 sync.Mutex 的 Go 结构体写了用值接收者的方法又到处用值传递把它传来传去、自以为加了锁就线程安全了,结果高并发下那份本该被锁保护的数据还是被改乱了计数器还是丢更新,排查很久才搞懂我每次值拷贝这个结构体时把里面的锁也一起复制成了另一把副本锁的根本不是同一把锁的深度复盘
我写了个带计数功能的结构体 Counter 里有个 sync.Mutex 保护并发、方法用了值接收者 func (c Counter) Inc() 里面 c.mu.Lock() defer Unlock() c.count++,然后多个 goroutine 并发调 Inc() 还经常把 Counter 当参数值传递。上线后:开 1000 个 goroutine 各 Inc() 一次最后 Value…- 0
- 0
-
我在 Go 的 for-select 循环里用 time.After 做超时、自以为简洁标准,结果服务跑久了内存一路缓慢上涨、profile 一看堆里全是没释放的定时器,最后才搞懂 time.After 每次求值都新建一个定时器而它要等到到期那一刻才会被回收的深度复盘
我有个后台 goroutine 在 for 循环里用 select 处理一个高频消息 channel,同时想加一个多久没消息就做点别的的超时,很自然写成 case- 6
- 0
-
我在 Go 的 for 循环里写 select 处理 channel、为了让它别卡住顺手加了个 default 分支,结果服务一启动某个 CPU 核心就直接飙到百分之百风扇狂转,我盯着那段逻辑明明没干什么重活百思不得其解最后才反应过来那个 default 让 select 永远不阻塞整个循环变成了疯狂空转的忙轮询的深度复盘
我有个后台 goroutine 在 for 循环里用 select 监听几个 channel(收任务、收停止信号),担心万一所有 channel 都没消息 select 会卡住、就给它加了个 default 分支,想着没消息时走 default 不卡住挺稳妥,功能也正常。可一上线就发现:服务明明很闲没什么任务、某个 CPU 核心却常年 100% 风扇狂转、整体性能还下降了。top 看是我的进程吃满…- 0
- 0
-
我的 Go 服务从一个 interface 里取值时写了 s := val.(string)、平时跑得稳稳的,某天上游传进来一个不是字符串的值整个服务当场 panic 崩溃,我盯着堆栈愣了半天才想起来 Go 的类型断言还有个带逗号 ok 的安全形式而我图省事用的那个单返回值版本断言失败是会直接炸的深度复盘
我有段 Go 代码从一个存着 any(interface{})的地方取出一个值,我知道它应该是字符串就直接 s := val.(string) 拿来用,上线后跑了很久平安无事,因为上游一直传的都是字符串。直到某天上游因为一个边缘场景传进来一个 nil(或一个数字),整个服务就崩了:日志里一行刺眼的 panic: interface conversion: interface {} is nil, …- 3
- 0
-
我的 Go 程序某个功能莫名其妙地卡住、既不报错也不返回,goroutine 越积越多最后内存涨爆,排查半天才发现是一个 channel 忘了 make 初始化、它是 nil,而往 nil channel 收发竟然不是报错、而是永久阻塞的深度复盘
我有段 Go 代码用 channel 在 goroutine 间传数据,在结构体里声明了一个 channel 字段,启动 goroutine 往里发另一边收,自测小场景跑通了。可某功能上线后行为诡异:它莫名卡住——既不报错也不返回也不超时,就静静挂着;随请求进来卡住的 goroutine 越积越多内存一路涨爆。我以为是死锁、下游慢,查半天没问题。直到打 goroutine 栈,发现一大堆都阻塞在那…- 0
- 0
-
我把两个结构体用 == 比一下是否相等,大多数时候都好好的,可某天程序突然 panic 挂了、报 comparing uncomparable type,排查半天才发现这个结构体里多了个切片字段、而 Go 里压根不是所有类型都能用 == 比较的深度复盘
我有个 Go 结构体,平时拿两个实例用 == 比是否相等(判重、查变化),一直用得好好的,编译从不报错。后来需求变了我给它加了个切片字段,代码照常编译通过测试也大体能跑,我没多想。可上线后程序某个时刻突然 panic:runtime error: comparing uncomparable type。我盯着报错那行 a == b 一头雾水:这不就是个普通相等比较吗,之前一直好好的凭什么突然不可比…- 0
- 0
-
我用 Go 遍历一个 map 生成结果,本地跑得好好的、顺序也对,可一上线就时不时输出顺序乱掉、还有个测试三天两头随机失败,排查半天发现 Go 故意把 map 的遍历顺序做成了随机的深度复盘
我有段 Go 代码把数据放进 map、再 for range 遍历拼成输出(配置、报表、签名串)。本地反复跑顺序总是稳定、输出也对,我理所当然认为遍历 map 就是按这个顺序,顺顺当当上了线。可上线后诡异:同样的数据生成的结果顺序时不时变样,大多数时候对、偶尔就乱;更抓狂的是一个依赖这输出的单测三天两头随机失败,本地十次九次过、CI 时不时红一次重跑又绿,像闹鬼。我一度怀疑并发和数据竞争查了半天没…- 0
- 0
-
我的 HTTP 请求明明已经超时返回了,可它在后台启动的 goroutine 还在埋头跑、下游调用也没停,goroutine 越积越多内存一路涨,因为我没把 context 的取消信号传下去也没人监听它的深度复盘
我有个接口处理请求时会启动一些 goroutine 做并行子任务,并给请求设了超时。线上现象诡异:有些请求超时返回了,可监控显示 goroutine 数量只增不减、内存一路缓慢上涨。排查发现那些超时的请求主流程返回了,但它启动的 goroutine 还在后台埋头跑(还在查早已没人要结果的下游)、跑完没人收、白白耗着然后泄漏。复盘才搞懂:Go 用 context 传播取消/超时,请求超时时 ctx …- 5
- 0
-
我在 Go 里用 == 判断一个错误是不是记录不存在,一直好好的,直到某层代码用 %w 把这个错误包装了一下再往上抛,我的 == 判断就突然失效了,把记录不存在当成了未知错误的深度复盘
我要区分记录不存在(返回 404)和真的出错了(返回 500),写成了 if err == sql.ErrNoRows。一直跑得好好的,直到某次重构、中间一层 DAO 在错误往上传时用 fmt.Errorf("query user failed: %w", err) 把原始错误包装了一层。从那以后我的 == 判断突然失效:明明底层就是 sql.ErrNoRows,== 却返回 …- 0
- 0
-
我用 WaitGroup 等一批 goroutine 全部干完,却把 wg.Add 写进了 goroutine 内部,结果主协程的 Wait 在 Add 之前就跑完直接返回,程序以为活儿都干完了其实大半还没开始:一次 WaitGroup 时序用错的深度复盘
我要启动一批 goroutine 并发处理任务、再用 WaitGroup 等它们全部干完汇总结果,写成了在循环里 go func(){ wg.Add(1); defer wg.Done(); doWork() }()——把 wg.Add(1) 放在了 goroutine 内部。线上现象诡异:不报错,但汇总结果经常不完整,明明启动 100 个任务却只有零星几个的结果。复盘才搞懂:WaitGroup …- 0
- 0
-
我用一个 channel 收集多个 worker 的结果,某个 worker 干完顺手把 channel 关了,其他还在干活的 worker 一发送就 panic:send on closed channel,一次 Go channel 关闭所有权的深度复盘
我用多个 worker goroutine 并发处理任务,每个 worker 把结果发到一个共享的 results channel,主 goroutine 收集。为了发完就通知结束,我让每个 worker 干完后 close(results)。功能偶尔能跑,可线上常崩 panic: send on closed channel。推演时序才明白:这个 channel 是多个 worker 共享发送的…- 0
- 0
-
我在一个循环里处理几千个文件、每个都顺手 defer 关闭,结果跑到一半报 too many open files,因为那些 defer 全攒到函数返回才执行:一次 Go defer 在循环里堆积的深度复盘
我有个函数要在 for 循环里依次处理几千个文件,每打开一个就顺手 defer file.Close() 确保关闭——这是 Go 管理资源的标准姿势。可线上跑到几百上千个文件时就报 too many open files 崩了。查清 defer 的执行时机才明白:defer 注册的函数是在当前函数返回时才执行,而不是当前循环迭代结束时;我在循环里 defer,这几千个 Close 并没有在每次迭代…- 0
- 0
-
我读那个 map 一直好好的,直到某次往里写了一个键,程序当场 panic:assignment to entry in nil map,一次 Go 里 nil map 读得写不得、未初始化被读操作掩盖的深度复盘
我有个 struct 里有个 map 字段 cache,某些地方读它一直没问题;直到某次代码走到一个分支往 cache 里写 m.cache[key]=value——当场 panic: assignment to entry in nil map,goroutine 崩了。这 map 前面明明读得好好的,怎么一写就说是 nil?捋一遍才明白:这个 cache 从来没 make 过、值是 nil(m…- 0
- 0
-
我在 for range 循环里起了一批 goroutine 并发处理任务,结果它们全都处理了同一个、也就是最后一个任务:一次 Go 循环变量被复用、闭包捕获到的全是最后一个值的深度复盘
我要并发处理一批任务,for range 遍历任务列表、每个任务起一个 goroutine 去处理。结果诡异:明明有 10 个不同任务,日志里 10 个 goroutine 处理的全是同一个、也就是最后一个任务,前 9 个一个都没处理。查清才发现:在 Go 1.22 之前,for range 的循环变量 task 是整个循环复用的同一个变量,每轮只是赋新值;而 goroutine 闭包捕获的是这个…- 0
- 0
-
一个每次请求都起一个 goroutine 却没人保证它能退出的服务,goroutine 越积越多、内存缓慢上涨,跑几天就 OOM:一次 goroutine 泄漏的深度复盘
服务内存缓慢上涨、跑几天就被 OOM,pprof 一看 goroutine 从几十个涨到几十万、只增不减,且全卡在同一行 channel 接收/发送上。根因是每请求起 goroutine 通过无缓冲 channel 返回结果,而主流程超时/取消后提前返回、不再接收,那个负责发送的 goroutine 就永远阻塞在 ch- 0
- 0
-
一个被多个 goroutine 同时读写的普通 map,把整个 Go 服务以 fatal error 直接干崩、连 recover 都拦不住:一次 map 并发不安全的深度复盘
高并发 Go 服务平时稳如磐石,一到高峰就毫无征兆整个进程挂掉,日志只留一行 fatal error: concurrent map read and map write,而且入口的 recover 居然拦不住。根因是用普通 map 做内存缓存、多个 goroutine 无同步地并发读写它——Go 内置 map 为性能不做并发同步,并发写会破坏其哈希结构,运行时检测到就抛不可 recover 的 …- 2
- 0
-
一次对 Go slice 做切片后 append,意外覆盖了原始 slice 里的数据,让一份订单列表凭空窜了值:一次共享底层数组的深度复盘
只往一段子切片里 append 了几个元素,原始订单 slice 紧挨其后的数据却被莫名其妙改掉了。根因是 Go 的 slice 切片(s[0:3])不复制数据、和原 slice 共享同一底层数组,且子切片的 cap 到底层数组末尾、有剩余容量,append 便原地写入、覆盖了原 slice 的元素;而 cap 不够时 append 会扩容分配新数组、不影响原数组——故行为时灵时不灵、极难复现。本…- 7
- 0
-
我在循环里用 defer 关闭打开的文件,以为每次循环结束就关了,结果它们全堆到函数返回才一起关,跑到一半就 too many open files,我对着 defer 在函数返回时才执行这个坑排查大半天的复盘
一个让我对 Go 的 defer 到底什么时候执行彻底搞清楚的坑。defer 是个优雅方便的特性(确保资源一定释放)我用得很顺手,可我用错了它的时机——以为 defer file.Close() 会在当前这次循环结束时关闭文件,实际它要等整个函数返回时才关。一个函数循环处理几千个文件,每个 os.Open 后 defer f.Close() 确保关闭,看起来是 Go 推荐的惯用法,可跑到一半 to…- 0
- 0
-
我用一个 map 做缓存,多个 goroutine 并发读写它,平时好好的,一到高并发就 fatal error: concurrent map writes 整个进程崩掉,连 recover 都拦不住,我对着 Go 的 map 不是并发安全的这个坑排查大半天的复盘
一个让我对 Go 并发安全彻底敬畏的坑,可怕在它不是普通 panic(还能 recover 兜住)而是一个直接让整个进程崩溃、recover 都拦不住的 fatal error,触发它的只是多个 goroutine 同时读写一个 map。高并发服务用一个 map 做内存缓存,Get 读 Set 写都不加锁,多 goroutine 并发访问。低并发时好好的就上线了,一到真正高并发就毫无征兆崩溃:fa…- 5
- 0
-
我的函数明明返回了一个 nil 的错误指针,调用方 if err != nil 却判定它不为 nil 进了错误分支,我对着 Go 里 nil 指针赋给接口后接口不等于 nil 这个坑排查了大半天的复盘
一个让我对 Go 的 interface 从以为懂到真正懂的烧脑坑,诡异到我一度怀疑 == 坏了:我返回的明明是 nil,调用方判断 != nil 却不是 nil。写了个返回 error 的函数,习惯性先声明具体错误类型指针 var e *MyError = nil,没出错时它保持 nil,最后 return e。传 true 不出错、e 一直是 nil,可调用方 if err != nil 却成…- 0
- 0
-
我从一个大切片里截了个子切片,往子切片 append 了个元素,结果原切片里好端端的数据竟被悄悄改掉了,我对着 Go 切片 append 共享底层数组这个坑排查大半天的复盘
一个让我对 Go slice 又爱又怕的经典坑,可怕在代码逻辑上看不出破绽、数据却在你没动它的情况下被悄悄改掉。需求很普通:有个存原始数据的大切片,从中截一段做处理、处理时会往子切片 append 新元素。我写 original := []int{1,2,3,4,5};sub := original[0:3];sub = append(sub, 999)。打印发现 sub 是 [1 2 3 999…- 0
- 0
-
我在 for 循环里处理一批文件、每个都 defer f.Close(),结果跑到一半就报 too many open files,我对着 defer 的执行时机排查了大半天的复盘
写了个 Go 批处理:一个函数里 for 循环遍历几千个文件,每个打开、处理、defer f.Close() 关闭,自觉很规范每个打开都配了 defer Close。结果跑到一半就崩:too many open files。困惑——我明明每个文件都 defer Close 了啊怎么还打开太多?排查大半天才理解 defer 一个关键却极易忽略的特性:defer 是在"函数返回时"…- 0
- 0
-
我的 Go 程序往一个 struct 里的 map 字段写值就 panic,可同一个 struct 里的 slice 字段 append 却好好的,我对着 nil map 排查了大半天的复盘
用 Go 写数据聚合,定义了个 struct 里有 map 字段和 slice 字段,创建实例后往 slice append 元素一切正常,可往 map 写值那行直接 panic: assignment to entry in nil map。满脸问号:同一个 struct、同样创建后就用、同样是集合类型,凭什么 slice 能用 map 就崩?排查大半天才理解 Go 的零值哲学,以及 nil m…- 0
- 0
-
我在 for 循环里起了一堆 goroutine 处理每个元素,结果它们全在处理同一个、还是最后一个,我对着循环变量被闭包捕获排查了大半天的复盘
用 Go 写批处理,想为切片每个元素并发起一个 goroutine,经典的 for + go 写法,行云流水。结果目瞪口呆:10 个元素起了 10 个 goroutine,处理的却全是同一个、还是最后一个,前 9 个一个没处理。盯着代码反复看,循环明明遍历了每个元素,凭什么 goroutine 全拿到最后一个?排查大半天才理解 Go 1.22 之前坑了无数人的经典陷阱:for 循环变量被闭包捕获。…- 0
- 0
Go
幸运之星正在降临...
点击领取今天的签到奖励!
恭喜!您今天获得了{{mission.data.mission.credit}}积分
我的优惠劵
-
¥优惠劵使用时效:无法使用使用时效:
之前
使用时效:永久有效优惠劵ID:×
没有优惠劵可用!
























