fiber的radix树路由在app.listen()启动后即冻结,禁止运行时注册(如handler内调用app.add()),否则直接panic;设计上仅支持启动前静态构建,运行时匹配全程无锁、只读。

Fiber 的路由树本身不支持运行时写操作,所有加锁都该避开它
为什么不能在 handler 里调用 app.Get() 或 app.Add()
Fiber 的 Radix 树在 app.Listen() 启动后就冻结了。任何运行时路由注册都会直接 panic,比如:panic: cannot register routes after starting the server。这不是锁没加好,而是设计上禁止——树结构只读,匹配过程无锁,靠的是启动前一次性构造完成。
- 错误做法:在某个
POST /api/dynamic-routehandler 里尝试app.Post("/new", handler) - 正确思路:把动态逻辑移到外部状态(如
sync.Map、Redis),路由只做分发入口 - 若真需热更新,应重启服务或用配置中心+ graceful reload,而非现场改树
sync.RWMutex 是高频读写最常用的保护手段
当多个 goroutine 需要共享访问一个 map、slice 或 struct 字段时,sync.RWMutex 比 sync.Mutex 更合适:读多写少场景下,允许多个 reader 并发,仅写时独占。
- 读操作用
mu.RLock()+defer mu.RUnlock(),开销极小 - 写操作必须用
mu.Lock()+defer mu.Unlock(),阻塞所有读写 - 别把整个 handler 包进锁里——只锁真正共享的临界区,比如更新计数器那几行
- 避免死锁:始终按固定顺序获取多个锁,或用
sync.Once替代重复初始化
用 sync.Map 替代手动加锁的常见 map 操作
如果只是存取键值对,且 key 类型是 string/int 等可比较类型,sync.Map 是更轻量的选择。它内部做了分片和读写分离,比包一层 sync.RWMutex + map 更适合高并发。
-
sync.Map.Load()和sync.Map.Store()是线程安全的,无需额外锁 - 不支持遍历(
Range()是快照语义),所以不适合需要全量扫描的场景 - 如果写远少于读,且 key 分布较均匀,
sync.Map性能通常优于加锁 map - 注意:它不是通用 map 替代品——比如不能用
len()获取大小,得自己维护计数器
全局状态误入闭包是 Fiber 并发最隐蔽的坑
Fiber 的 handler 函数常被复用多次,如果闭包捕获了外部可变变量(如 var counter int),所有请求会竞争修改同一份内存,结果不可预测。
- 典型错误:
app.Get("/inc", func(c *fiber.Ctx) error { counter++; return c.SendString(strconv.Itoa(counter)) }) - 修复方式:要么用
sync.AtomicInt64做原子递增,要么把状态存在c.Locals或 context 中,避免跨请求共享 - 更彻底的解法:把状态下沉到数据库或 Redis,让应用层回归无状态
- 检查点:所有 handler 内部使用的变量,如果不是只读常量,就得确认其生命周期和并发安全性
真正难的不是选哪个锁,而是识别哪些数据真的需要锁——Fiber 的高性能正来自它默认不碰共享状态。一旦你开始频繁加锁,说明设计可能已偏离它的使用范式。











