Golang实现并发安全的随机数生成
Golang实现并发安全的随机数生成
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
// 使用 rand.Intn 示例
// 伪随机序列号
// 默认情况下,它依赖于某个种子数
// Intn 返回一个在 [0,n) 范围内的非负伪随机整数。如果 n <= 0,则会引发 panic。
fmt.Println("我最喜欢的数字是 ", rand.Intn(50))
// 类型 Rand
// func New(src Source) *Rand -
// New 返回一个新的 Rand,它使用来自 src 的随机值来生成其他随机值
// time 包
// func(t Time) UnixNano() int64
// UnixNano 将 t 作为 Unix 时间返回,即自 1970 年 1 月 1 日 UTC 以来经过的纳秒数。
// 如果纳秒级的 Unix 时间无法用 int64 表示(即 1678 年之前或 2262 年之后的日期),则结果未定义。
// 请注意,这意味着对零 Time 调用 UnixNano 的结果是未定义的。结果不依赖于与 t 关联的位置。
customSource := rand.NewSource(time.Now().UnixNano())
customRand := rand.New(customSource)
fmt.Println(customRand.Intn(100))
// 使用 rand.Seed
// Seed 使用提供的种子值将默认 Source 初始化为确定状态。
// 如果未调用 Seed,生成器的行为就像被 Seed(1) 播种一样。
// 除以 2³¹-1 后余数相同的种子值会生成相同的伪随机序列。
// 与 Rand.Seed 方法不同,Seed 对于并发使用是安全的。
rand.Seed(time.Now().UnixNano())
fmt.Println("使用 seed 函数:", rand.Intn(200))
}
更多关于Golang实现并发安全的随机数生成的实战教程也可以访问 https://www.itying.com/category-94-b0.html
skillian: 你需要真正并行的随机数
完美,你读懂了我的心思。很好的回答。"*rand.Rand" 这正是我想要的。谢谢
更多关于Golang实现并发安全的随机数生成的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你的问题是什么?根据你从 math/rand 和 time 包文档中复制的信息,我假设你的问题是“在 Go 中创建随机数的最佳方式是什么?”
根据 math/rand 文档:
随机数由源(Source)生成。顶层函数,例如 Float64 和 Int,使用一个默认的共享源,该源在每次程序运行时产生一个确定性的值序列。如果每次运行需要不同的行为,请使用 Seed 函数来初始化默认源。默认源对于多个 goroutine 的并发使用是安全的,但由 NewSource 创建的源则不是。
因此,你可以使用 rand 中的任何顶层函数来并发地获取随机数。这些实现背后使用了 sync.Mutex,所以如果你需要真正并行的随机数生成,那么你应该为每个需要随机数的 goroutine 创建一个单独的 *rand.Rand 实例。
在Go语言中实现并发安全的随机数生成,推荐使用rand.NewSource()创建独立的随机数生成器,或者使用sync.Mutex保护共享的随机数生成器。以下是两种实现方式:
方案一:每个goroutine使用独立的随机数生成器
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func generateRandom(wg *sync.WaitGroup, id int) {
defer wg.Done()
// 每个goroutine有自己的随机源,避免竞争
source := rand.NewSource(time.Now().UnixNano() + int64(id))
r := rand.New(source)
for i := 0; i < 3; i++ {
fmt.Printf("Goroutine %d: %d\n", id, r.Intn(100))
time.Sleep(time.Millisecond * 10)
}
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go generateRandom(&wg, i)
}
wg.Wait()
}
方案二:使用互斥锁保护共享的随机数生成器
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
type SafeRandom struct {
mu sync.Mutex
r *rand.Rand
}
func NewSafeRandom() *SafeRandom {
return &SafeRandom{
r: rand.New(rand.NewSource(time.Now().UnixNano())),
}
}
func (sr *SafeRandom) Intn(n int) int {
sr.mu.Lock()
defer sr.mu.Unlock()
return sr.r.Intn(n)
}
func worker(id int, sr *SafeRandom, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 3; i++ {
num := sr.Intn(100)
fmt.Printf("Worker %d: %d\n", id, num)
time.Sleep(time.Millisecond * 10)
}
}
func main() {
var wg sync.WaitGroup
sr := NewSafeRandom()
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, sr, &wg)
}
wg.Wait()
}
方案三:使用rand.NewSource的并发安全版本
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
results := make(chan string, 15)
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
// 使用不同的种子创建独立的随机源
source := rand.NewSource(time.Now().UnixNano() + int64(id))
rng := rand.New(source)
for j := 0; j < 3; j++ {
results <- fmt.Sprintf("Goroutine %d: %d", id, rng.Intn(100))
time.Sleep(time.Millisecond * 5)
}
}(i)
}
go func() {
wg.Wait()
close(results)
}()
for result := range results {
fmt.Println(result)
}
}
重要说明:
-
不要使用
rand.Seed():从Go 1.20开始,rand.Seed()已被弃用,全局随机数生成器会自动初始化。 -
并发安全原则:
- 每个goroutine使用独立的
rand.Rand实例 - 或者使用互斥锁保护共享的
rand.Rand实例 - 避免并发访问同一个
rand.Rand实例
- 每个goroutine使用独立的
-
性能考虑:方案一(独立实例)通常比方案二(互斥锁)性能更好,特别是在高并发场景下。
-
种子管理:确保为每个随机源使用不同的种子,可以使用时间戳加上goroutine ID或其他唯一标识。

