这种同时与其他库一起使用 ulid 的方式安全吗?

huangapple go评论149阅读模式
英文:

Is this a safe way to use ulid concurrently with other libraries too?

问题

我正在尝试第一次使用 ulid 包

它们的 README 中提到:

> 请注意,math 包中的 rand.Rand 不适用于并发使用
> 每个长时间运行的 Go 协程实例化一个,或者如果你想避免在包级别函数中频繁观察到的锁定 rand.Source 的潜在争用,可以使用 sync.Pool

你能帮我理解这是什么意思,以及如何编写与 entgqlgen 等库一起安全使用的代码吗?

例如:我在我的应用程序中使用下面的代码生成新的 ID(有时甚至在同一毫秒内生成多个 ID,这对于 ulid 来说是可以接受的)。

import (
  "math/rand"
  "time"

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

var defaultEntropySource *ulid.MonotonicEntropy

func init() {
  defaultEntropySource = ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)
}

func NewID() string {
  return ulid.MustNew(ulid.Timestamp(time.Now()), defaultEntropySource).String()
}

这种使用方式安全吗?

英文:

I'm trying to use for the first time the ulid package.

In their README they say:

> Please note that rand.Rand from the math package is not safe for concurrent use.
> Instantiate one per long living go-routine or use a sync.Pool if you want to avoid the potential contention of a locked rand.Source as its been frequently observed in the package level functions.

Can you help me understand what does this mean and how to write SAFE code for concurrent use with libraries such ent or gqlgen?

Example: I'm using the below code in my app to generate new IDs (sometimes even many of them in the same millisecond which is fine for ulid).

import (
  "math/rand"
  "time"

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

var defaultEntropySource *ulid.MonotonicEntropy

func init() {
  defaultEntropySource = ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)
}

func NewID() string {
  return ulid.MustNew(ulid.Timestamp(time.Now()), defaultEntropySource).String()
}

Is this a safe way to use the package?

答案1

得分: 2

这是使用该包的安全方式吗?

不,该句子表明每个rand.Source应该是局部于goroutine的,你的defaultEntropySource rand.Source可能在多个goroutine之间共享。

根据文档New函数,你只需要确保熵读取器在并发使用时是安全的,但Monotonic不是。以下是实现文档建议的两种方式:

每次调用NewID()时创建一个单独的rand.Source,为每次调用NewID()分配一个新的熵

func NewID() string {
	defaultEntropySource := ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)
	return ulid.MustNew(ulid.Timestamp(time.Now()), defaultEntropySource).String()
}

Playground

与上述方法类似,但使用sync.Pool来可能重用先前分配的rand.Source

var entropyPool = sync.Pool{
	New: func() any {
		entropy := ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)
		return entropy
	},
}

func NewID() string {
	e := entropyPool.Get().(*ulid.MonotonicEntropy)
	s := ulid.MustNew(ulid.Timestamp(time.Now()), e).String()
	entropyPool.Put(e)
	return s
}

Playground

英文:

> Is this a safe way to use the package?

No, that sentence suggests that each rand.Source should be local to the goroutine, your defaultEntropySource rand.Source piece is potentially shared between multiple goroutines.

As documentated New function, you only need to make sure the entropy reader is safe for concurrent use, but Monotonic is not. Here is a two ways of implementing the documentation suggestion:

Create a single rand.Source per call o NewID(), allocates a new entropy for each call to NewID

func NewID() string {
	defaultEntropySource := ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)
	return ulid.MustNew(ulid.Timestamp(time.Now()), defaultEntropySource).String()
}

Playground

Like above but using sync.Pool to possible reuse previously allocated rand.Sources

var entropyPool = sync.Pool{
	New: func() any {
		entropy := ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)
		return entropy
	},
}

func NewID() string {
	e := entropyPool.Get().(*ulid.MonotonicEntropy)
	s := ulid.MustNew(ulid.Timestamp(time.Now()), e).String()
	entropyPool.Put(e)
	return s
}

Playground

huangapple
  • 本文由 发表于 2022年6月11日 21:08:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/72584598.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定