golang math/rand 的协程安全问题

math/rand

math/rand 是 golang 官方自带的随机数库

今天看 grpc-go 代码时,才发现,原来 math/rand 不能算协程安全的库

看官方文档说明(摘自 GOROOT/src/math/rand/rand.go 1 - 19 行):

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package rand implements pseudo-random number generators.
//
// Random numbers are generated by a Source. Top-level functions, such as
// Float64 and Int, use a default shared Source that produces a deterministic
// sequence of values each time a program is run. Use the Seed function to
// initialize the default Source if different behavior is required for each run.
// The default Source is safe for concurrent use by multiple goroutines, but
// Sources created by NewSource are not.
//
// Mathematical interval notation such as [0, n) is used throughout the
// documentation for this package.
//
// For random numbers suitable for security-sensitive work, see the crypto/rand
// package.
package rand

关键一句:

// The default Source is safe for concurrent use by multiple goroutines, but
// Sources created by NewSource are not.

也就说,不初始化种子的默认随机源,用 rand 库是协程安全的。

然而现实上,用随机库,一定要初始化种子…

因此,可以视 math/rand 库就是非协程安全的!

grpc-go 的解决方案

grpc-go 自己封装了下 math/rand 库,以确保随机数能多协程安全使用。

代码非常简单(摘自 https://github.com/grpc/grpc-go/blob/master/internal/grpcrand/grpcrand.go):

/*
 *
 * Copyright 2018 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

// Package grpcrand implements math/rand functions in a concurrent-safe way
// with a global random source, independent of math/rand's global source.
package grpcrand

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

var (
	r  = rand.New(rand.NewSource(time.Now().UnixNano()))
	mu sync.Mutex
)

// Int63n implements rand.Int63n on the grpcrand global source.
func Int63n(n int64) int64 {
	mu.Lock()
	res := r.Int63n(n)
	mu.Unlock()
	return res
}

// Intn implements rand.Intn on the grpcrand global source.
func Intn(n int) int {
	mu.Lock()
	res := r.Intn(n)
	mu.Unlock()
	return res
}

// Float64 implements rand.Float64 on the grpcrand global source.
func Float64() float64 {
	mu.Lock()
	res := r.Float64()
	mu.Unlock()
	return res
}

grpc-go 静态检查确保库引用正确

因为 IDE 都具备 autoimport 功能,可能错误导入 golang 的 math/rand

因此 grpc-go 编译前,都会自动执行 ./vet.sh 做静态检查 (摘自 82 - 84 行) :

# - Do not import math/rand for real library code.  Use internal/grpcrand for
#   thread safety.
git grep -l '"math/rand"' -- "*.go" 2>&1 | not grep -v '^examples\|^stress\|grpcrand\|^benchmark\|wrr_test'

你可能感兴趣的:(Go语言杂文,math/rand,golang,grpc-go,静态检查,协程安全)