You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go并发场景下与其他库共用ulid的安全写法及示例校验

结论先行

你当前写的ULID生成代码不是并发安全的,高并发场景下会出现随机数序列错乱、ID重复甚至程序panic的问题。

官方提示含义说明

math/rand包提供的rand.Rand结构体本身没有做并发访问保护:

  • 多个goroutine同时调用同一个rand.Rand实例的生成方法时,会触发内部状态竞态
  • math/rand包暴露的包级随机函数虽然做了并发安全处理,但靠全局锁实现,高并发下锁争用开销很高
  • 官方给出的两个优化方向本质都是避免多个goroutine共用同一个非线程安全的随机源实例,从根源上消除竞态和不必要的锁开销
兼容ent、gqlgen的并发安全实现

两种方案都只需要暴露统一的NewID方法,第三方库直接调用该方法即可,不需要额外适配。

方案1:sync.Pool管理熵源(推荐,高并发性能最优)

通过对象池复用熵源实例,既避免跨goroutine共用随机源,又减少重复初始化的开销,是生产环境最常用的实现:

import (
	"math/rand"
	"sync"
	"time"

	"github.com/oklog/ulid/v2"
)

var entropyPool = sync.Pool{
	New: func() any {
		// 每个池内实例绑定独立的随机源,互不干扰
		return ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)
	},
}

// NewID 生成并发安全的ULID字符串
func NewID() string {
	entropy := entropyPool.Get().(*ulid.MonotonicEntropy)
	defer entropyPool.Put(entropy)
	return ulid.MustNew(ulid.Timestamp(time.Now()), entropy).String()
}

方案2:互斥锁保护全局熵源(实现简单,适合中低并发场景)

如果业务并发量不高,直接加锁保护全局熵源即可,代码维护成本更低:

import (
	"math/rand"
	"sync"
	"time"

	"github.com/oklog/ulid/v2"
)

var (
	defaultEntropy = ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)
	entropyLock    sync.Mutex
)

// NewID 生成并发安全的ULID字符串
func NewID() string {
	entropyLock.Lock()
	defer entropyLock.Unlock()
	return ulid.MustNew(ulid.Timestamp(time.Now()), defaultEntropy).String()
}
注意事项
  • 不要直接用math/rand包的全局包级函数作为ULID熵源,高并发下全局锁的性能损耗非常明显
  • 不需要为每个goroutine永久初始化独立熵源,用sync.Pool复用的方式已经能满足性能和安全要求,内存开销更低
  • 如果你需要在ent的schema钩子、gqlgen的resolver里生成ULID,直接调用上述NewID方法即可,不需要额外修改配置

内容的提问来源于stack exchange,提问作者Fred Hors

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 17:27:22