Go语言进阶五——并发安全和锁

217 阅读2分钟

我正在参加「掘金·启航计划」

有时候go代码中可能会存在多个goroutine同时操作一个资源,这种情况会发生竞态问题,类比火车上卫生间被车厢的人竞争。

例:

var wg sync.WaitGroup
var x int = 0

func hello() {
	for i := 0; i < 5000; i++ {
		x += 1
	}
	wg.Done()
}

func main() {
	wg.Add(2)
	go hello()
	go hello()
	wg.Wait()
	fmt.Println(x)//输出可能不等于10000
}

互斥锁

互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个gotoutine可以访问共享资源,go语言使用sync包的Mutex类型来实现互斥锁,使用互斥锁能保证同一时间有且只有一个gotoutine进入临界区,其他的gotoutine则在等待锁。

//sync.Mutex 互斥锁
var wg sync.WaitGroup
var lock sync.Mutex
var x int = 0

func hello() {
	for i := 0; i < 5000; i++ {
		lock.Lock() //加锁
		x += 1
		lock.Unlock()//解锁
	}
	wg.Done()
}

func main() {
	wg.Add(2)
	go hello()
	go hello()
	wg.Wait()
	fmt.Println(x)//正常输入10000
}

读写互斥锁

互斥锁是完全互斥的,但是很多实际的场景下是读多写少的,我们并发读的时候不涉及资源修改的时候没必要枷锁,这种场景下使用读写锁是更好的一种选择,读写锁在go语言中使用sync包中的RWMutex类型

读写锁分为两种:读锁和写锁,当一个goroutine获取读锁之后,其他的gotoutine如果是获取读锁会继续获得锁,如果获取写锁就会等到,当一个goroutine获取写锁之后,其他的gotoutine无论是获取读锁还是写锁都会等待。

需要注意的是读写锁适合读多写少的场景,如果读和写的操作差别不大,读写锁的优势不明显

示例:

var (
	wg     sync.WaitGroup
	x      int64 = 0
	lock   sync.Mutex
	rwlock sync.RWMutex
)

func read() {
	defer wg.Done()
	//lock.Lock() //加互斥锁
	rwlock.RLock() //加读锁
	time.Sleep(10 * time.Millisecond)
	//lock.Unlock()//解互斥锁
	rwlock.RUnlock() //解读锁
}

func write() {
	defer wg.Done()
	//lock.Lock()//加互斥锁
	rwlock.Lock() //加写锁
	x++
	//lock.Unlock()//解互斥锁
	rwlock.Unlock()
}

func main() {
	now := time.Now()
	for i := 10; i < 10; i++ {
		wg.Add(1)
		go write()
	}
	for i := 0; i <= 1000; i++ {
		wg.Add(1)
		go read()
	}
	wg.Wait()
	fmt.Println(time.Now().Sub(now)) //互斥锁耗时11.3s  读写锁耗时20.9ms
}