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

3 回复

skillian: 你需要真正并行的随机数

完美,你读懂了我的心思。很好的回答。"*rand.Rand" 这正是我想要的。谢谢

更多关于Golang实现并发安全的随机数生成的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你的问题是什么?根据你从 math/randtime 包文档中复制的信息,我假设你的问题是“在 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)
    }
}

重要说明:

  1. 不要使用rand.Seed():从Go 1.20开始,rand.Seed()已被弃用,全局随机数生成器会自动初始化。

  2. 并发安全原则

    • 每个goroutine使用独立的rand.Rand实例
    • 或者使用互斥锁保护共享的rand.Rand实例
    • 避免并发访问同一个rand.Rand实例
  3. 性能考虑:方案一(独立实例)通常比方案二(互斥锁)性能更好,特别是在高并发场景下。

  4. 种子管理:确保为每个随机源使用不同的种子,可以使用时间戳加上goroutine ID或其他唯一标识。

回到顶部