Go---锁

165 阅读2分钟

并发中的问题

并发编程中,有时候会有需要共享资源的场景,这时候并发就有可能出现一些问题。

package main

import (
	"fmt"
	"sync"
)

var (
	x = 0
	wg sync.WaitGroup
)

// 自增一万次
func add() {
	for i := 0; i < 10000; i++ {
		x++
	}
	wg.Done()
}

func main() {
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go add()
	}
	wg.Wait()
	fmt.Println(x)
}

如上面这段代码,x是一个共享变量,当10个goroutine同时去让x自增一万次时,期望得到的结果应该是100000,但实际结果不可预测:

PS D:\Go\lock> go run main.go
41979
PS D:\Go\lock> go run main.go
52654
PS D:\Go\lock> go run main.go
47636

结果每次都不相同,这是因为x++并不是一个原子操作,它分为x+1与将x+1赋值给x两部分,当某一个goroutine将x赋值为50000时,可能下一个goroutine又会把30000赋值给x,这就导致了最后x的结果不是正确的结果。

解决上面遇到的问题的方法就是加锁,使x的修改具有唯一性,即一次只能有一个goroutine修改x;

sync.Mutex

package main

import (
	"fmt"
	"sync"
)

var (
	x = 0
	// 声明一个锁
	lock sync.Mutex
	wg   sync.WaitGroup
)

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

func main() {
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go add()
	}
	wg.Wait()
	fmt.Println(x)
}

再x++执行前加锁,在其执行后解锁,这样无论执行多少次,结果都是正确的结果

PS D:\Go\lock> go run main.go
100000
PS D:\Go\lock> go run main.go
100000
PS D:\Go\lock> go run main.go
100000

sync.RWMutex

sync.Mutex是一个普通锁,即无论是读操作还是写操作都会加锁,而读操作不会影响资源,无论读多少次,资源都不会变,所以读的时候,其它goroutine完全没有必要等待解锁后才能执行读操作。

sync.RWMutex是一个读写锁,在一个goroutine获得读锁的时候,其它goroutine无需等待,可以继续获得读锁,只有当一个goroutine获得写锁的时候,其它goroutine才需要等待锁释放后才能继续获得锁,因为写操作影响了资源,写之前和写之后资源是不一样的,所以需要等待写锁释放后,资源已经被改变,才能继续获得锁以保证资源是正确的。

sync.RWMutex的使用和sync.Mutex差不多

package main

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

var (
	x = 0
	// 声明一个锁
	lock   sync.Mutex
	wrLock sync.RWMutex
	wg     sync.WaitGroup
)

func read() {
	for i := 0; i < 10000; i++ {
		// 加读锁
		wrLock.RLock()
		// 加普通锁
		//lock.Lock()
		//fmt.Println()
		// 解读锁
		wrLock.RUnlock()
		// 解普通锁
		//lock.Unlock()
	}
	wg.Done()
}

func write() {
	for i := 0; i < 100; i++ {
		// 加写锁
		wrLock.Lock()
		// 加普通锁
		//lock.Lock()
		x++
		// 解写锁
		wrLock.Unlock()
		// 解普通锁
		//lock.Unlock()
	}
	wg.Done()
}

func main() {
	oldTime := time.Now()
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go write()
	}
	wg.Add(10000)
	for i := 0; i < 10000; i++ {
		go read()
	}
	wg.Wait()
	fmt.Println("消耗时间:", time.Since(oldTime))
}


对比下时间消耗

读写锁:
消耗时间: 4.0886281s
普通锁:
消耗时间: 12.0778299s

通过对比可以看出,本例中两种锁的时间消耗差别是很大的。

end

文章仅做个人学习交流,也许并不完全正确,欢迎指正;