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'