+
defer
defer 执行循序为LIFO(后进先出),参数的值在defer语句执行时就已经确定。
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
输出
4 3 2 1 0
defer 并不是免费的。比如 defer Mutex.Unlock 比直接Mutex.Unlock开销更大
对于一些简短的加锁-修改某个值-释放锁的操作可以这么做。但是如果在加锁之后,可能在不同的分支都需要执行释放锁,还是建议直接使用defer Mutex.Unlock, 否则在(以后)修改或者新增分支的时候可能忘记释放锁从而引入bug。除非必要,应该以代码的可维护性为主。
var i int
func unlockDefer(l *sync.Mutex) {
l.Lock()
defer l.Unlock()
i = rand.Int()
}
func unlockDirect(l *sync.Mutex) {
l.Lock()
i = rand.Int()
l.Unlock()
}
func BenchmarkUnlockDefer(b *testing.B) {
l := &sync.Mutex{}
for n := 0; n < b.N; n++ {
unlockDefer(l)
}
}
func BenchmarkUnlockDirect(b *testing.B) {
l := &sync.Mutex{}
for n := 0; n < b.N; n++ {
unlockDirect(l)
}
}
BenchmarkUnlockDefer-4 20000000 56.9 ns/op
BenchmarkUnlockDirect-4 50000000 31.7 ns/op
切片和数组
array的长度是类型的一部分
[3]int[4]int
array 是传值的,值指的是整个数组的内容
func modify(a [3]int) {
a[0] = 0
}
func main() {
x := [...]int{3,2,1}
modify(x)
fmt.Println(x)
}
输出
[3 2 1]
slice 本质也是传值,但看起来像传递了底层数组的引用
golang 都是传值的,slice看起来像是传引用是因为slice的值本身包含了一个对底层数组的引用(指针)。通过查看下面的slice结构体描述就很清晰。src/runtime/slice.go
type slice struct {
array unsafe.Pointer
len int
cap int
}
==
==
slice 与内存模型
append 不是线程安全的
var s []int
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
s = append(s, i)
wg.Done()
}(i)
}
wg.Wait()
fmt.Println(s)
一种可能的输出:
[0 6 7 9]
a[x]与b[y]是否存在竞态(“竞态”就是在多线程的编程中,你在同一段代码里输入了相同的条件,但是会输出不确定的结果的情况。),与a、b是否为同一slice无关,只与a[x]与b[y]是否指向同一块内存区域有关
译:数组、片和结构类型的结构化变量具有可以单独寻址的元素和字段。每个这样的元素就像一个变量。
也就是说对于slice s而言,我们应该将s[i]看做是一个独立的变量(variable)。例如,对于下面这段代码,虽然a、b是两个完全不同是slice,但是a[1]与b[0]指向了同一块内存区域,等同于同一个变量。如果对a[1]与b[0]的并发访问不做可见性保护(如加锁),那么可能在代码中出现并发bug。(并不一定会触发这个bug,但是存在几率。因为现阶段groutine的加载速度没有main程序快,就和这个demo来说)
func main() {
a := []int{1,2}
b := a[1:]
go func() {
a[1] = 0
}()
fmt.Println(b[0])
}
go run -race ./
2
==================
WARNING: DATA RACE
Write at 0x00c0000a2008 by goroutine 6:
main.main.func1()
/Users/vicxiao/workspace/go/test/main.go:11 +0x47
Previous read at 0x00c0000a2008 by main goroutine:
main.main()
/Users/vicxiao/workspace/go/test/main.go:13 +0xaa
Goroutine 6 (running) created at:
main.main()
/Users/vicxiao/workspace/go/test/main.go:10 +0x98
==================
Found 1 data race(s)
a[1]=0a[0]=0
与[]byte相关的性能问题
slice 引用底层数组而造成内存泄露/GC压力
ioutil.ReadAll
复制数据时使用copy比append性能更优
import (
"crypto/rand"
"testing"
)
var (
src = make([]byte, 512)
dst = make([]byte, 512)
)
func genSource() {
rand.Read(src)
}
func BenchmarkCopy(b *testing.B) {
for n := 0; n < b.N; n++ {
b.StopTimer()
genSource()
b.StartTimer()
copy(dst, src)
}
}
func BenchmarkAppend(b *testing.B) {
for n := 0; n < b.N; n++ {
b.StopTimer()
genSource()
b.StartTimer()
dst = append(dst, src...)
}
}
注:
dst作为全局变量是防止编译器优化for-loop
测试时,机器不繁忙
uptime;go version;go test -bench=. ./
11:56:10 up 294 days, 14:58, 3 users, load average: 0.58, 0.52, 0.63
go version go1.14.1 linux/amd64
goos: linux
goarch: amd64
pkg: copyvsappend
BenchmarkCopy-40 9808320 116 ns/op
BenchmarkAppend-40 479055 8740 ns/op
PASS
ReadFullioutil.ReadAll
Content-Lengthio.ReadFullioutil.ReadAllReadAllReadFull
for range
如果每个元素比较大,循环时使用range 取值的方式遍历,性能较差
这个比较直观,因为有很多内存拷贝
var X [1 << 15]struct {
val int
_ [4096]byte
}
var Result int
func BenchmarkRangeIndex(b *testing.B) {
var r int
for n := 0; n < b.N; n++ {
for i := range X {
x := &X[i]
r += x.val
}
}
Result = r
}
func BenchmarkRangeValue(b *testing.B) {
var r int
for n := 0; n < b.N; n++ {
for _, x := range X {
r += x.val
}
}
Result = r
}
func BenchmarkFor(b *testing.B) {
var r int
for n := 0; n < b.N; n++ {
for i := 0; i < len(X); i++ {
x := &X[i]
r += x.val
}
}
Result = r
}
BenchmarkRangeIndex-4 10000 127986 ns/op
BenchmarkRangeValue-4 50 25939345 ns/op
BenchmarkFor-4 10000 127817 ns/op
这个有什么好的替代方法吗,比如直接使用for i 遍历数组长度?(从根本解决,要么不要创建大数组,要么大数组不要遍历)
embedding
*T*S
这个主要是防止引入bug,例如下面的并发控制器,错误地在外层将receiver定义为值类型。
type ConcurrentLocker struct{
sync.Map
}
type LeaveFunc func()
func (cl ConcurrentLocker) Enter(key string) (bool, LeaveFunc) {
if _, occupied := cl.LoadOrStore(key, struct{}{}); !occupied {
return true, func(){
cl.Delete(key)
}
}
return false, nil
}
func main() {
cl := ConcurrentLocker{}
key := "some-key"
fmt.Println(cl.Enter(key))
fmt.Println(cl.Enter(key))
}
这个错误的代码并不能达到并发控制的效果:
$ go run ./
true 0x10939e0
true 0x10939e0
* sync.MapEnter* ConcurrentLocker