go goroutine 学习笔记(二)

103 阅读2分钟
1. 锁

Go代码中存在多个goroutine同时操作一个资源的情况,导致结果与预期不符。锁是一种常用的来控制资源的方式,确保同一时刻只有一个goroutine 获取到了资源。

在 go 语言中,锁有两种:互斥锁,读写锁。

互斥锁: sync.Mutex。

读写锁:sync.RWMutex (读写互斥,读读不互斥)。

下面的代码是开启十个goroutine 对一个值进行加操作,每次运行的结果不一定是正确的。

package main

import (
	"fmt"
	"sync"
)

var sum int
var wg sync.WaitGroup

func cacl() {
	defer wg.Done()
	for i := 0; i < 50000; i++ {
		sum = sum + 1
	}
}
func main() {
	//开启10个goroutine
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go cacl()
	}
	wg.Wait()
	fmt.Printf("sum is %v", sum)
}

加操作不是原子操作,我们为了确保结果的准确需要对加操作加锁。

使用互斥锁修改上面的代码

package main

import (
	"fmt"
	"sync"
)

var sum int
var wg sync.WaitGroup

//声明锁
var myLock sync.Mutex

func cacl() {
	defer wg.Done()
	for i := 0; i < 50000; i++ {
		//加锁
		myLock.Lock()
		sum = sum + 1
		//释放锁
		myLock.Unlock()
	}
}
func main() {
	//开启10个goroutine
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go cacl()
	}
	wg.Wait()
	fmt.Printf("sum is %v", sum)
}

这样每次的结果就是正确的。

读写锁适合读多写少的情景。

下面的代码就模拟了一个读多写少的场景,这样的情况下,使用读写锁消耗的时间比使用互斥锁少的多。

package main

import (
	"fmt"
	"sync"
	"time"
)

//
var wg sync.WaitGroup

//声明读写锁
var rwlock sync.RWMutex

//模拟读数据
func readData() {
	defer wg.Done()
	//开启读锁
	rwlock.RLock()
	time.Sleep(time.Millisecond * 2)
	//释放读锁
	rwlock.RUnlock()

}

//模拟写数据
func writeData() {
	defer wg.Done()
	//开启写锁
	rwlock.Lock()
	time.Sleep(time.Millisecond * 10)
	//释放写锁
	rwlock.Unlock()

}

func main() {
	//开始时间
	start := time.Now()
	//设置1000个读
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go readData()
	}
	//设置10个写
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go writeData()
	}
	wg.Wait()
	end := time.Now()
	fmt.Println(end.Sub(start))
}

2. 原子操作

代码中的加锁操作涉及内核态的上下文切换会比较耗时、代价比较高, Go语言提供的原子操作方法它在用户态就可以完成,因此性能比加锁操作更好。

sync/atomic 中可以对几种简单的类型进行原子操作。

这些类型包括 int32、int64、uint32、uint64、uintptr、unsafe.Pointer 。

这些函数的原子操作共有5种:读取操作,写入操作,修改操作,交换操作,比较并交换操作。

使用atomic.Add() 来修改上面的代码

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

var sum int32
var wg sync.WaitGroup

func cacl() {
	defer wg.Done()
	for i := 0; i < 50000; i++ {
		atomic.AddInt32(&sum, 1)
	}
}
func main() {
	//开启10个goroutine
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go cacl()
	}
	wg.Wait()
	fmt.Printf("sum is %v", sum)
}

这样也能保证数据的准确。