Go语言同步与锁
在并发条件下,信息的同步要求用户必须用锁的机制来保证准确性。Go语言标准库中提供了互斥锁和读写锁供开发者使用。
Go语言包中的sync包提供了两种锁类型:sync.Mutex和sync.RwMutex,前者是互斥锁,后者是读写锁。
互斥锁
互斥锁是传统的并发程序对资源共享进行访问控制的主要手段,在Go语言中,更推崇使用通道来实现资源共享和通信,互斥锁具有两个公开方法:调用Lock()获得锁和调用Unlock()释放锁。
- 同一个协程中同步调用使用Lock()枷锁后,不能再对其加锁,只能在Unlock()之后再次Lock(),多个协程在Unlock()之后会产生锁竞争,来竞争下一次加锁的机会,因此互斥锁只允许有一个读或者写的场景,所以该锁也叫全局锁。
- Unlock()只用于解锁,Unlock()之前必须要有Lock()。已经锁定的Mutex并不与特定的协程相关联,这样可以利用一个协程对其加锁,再利用其它协程对其解锁。
接下来看一段代码,通过三个协程体现互斥锁对资源的访问控制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| package main
import ( "fmt" "sync" "time" )
func main(){ wg := sync.WaitGroup{} mutex := sync.Mutex{}
fmt.Println("Locking G0") mutex.Lock() fmt.Println("Locked G0") wg.Add(3)
for i:=1;i<4;i++{ go func(i int) { fmt.Printf("Locking G%d\n",i) mutex.Lock() fmt.Printf("Locked G%d\n",i)
time.Sleep(time.Second*2) mutex.Unlock() fmt.Printf("unlocked G%d\n",i) wg.Done() }(i) }
time.Sleep(time.Second*2) fmt.Println("ready unlocked G0") mutex.Unlock() fmt.Println("unlocked G0") wg.Wait() }
|
通过这段程序可以看到,只有当锁释放时,才能进行加锁动作,当G0的锁释放时,等待加锁的G1,G2,G3都会竞争加锁的机会。
读写锁
读写锁实质上是多读单写的互斥锁,分别针对读操作和写操作进行锁定和解锁操作,用于读次数远大于写次数的场合。读写锁具有四个公开的方法:写锁定Lock()、写解锁Unlock()、读锁定RLock()、读解锁RUnlock()。
- 写锁定时,对读写锁进行读锁定或写锁定时,都将阻塞。
- 读锁定时,对读写锁进行写锁定,将阻塞。但可多读。
- 写解锁之前必须要写锁定。读解锁之间必须要读锁定。
下面看一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package main
import ( "fmt" "sync" "time" )
var m *sync.RWMutex
func main() { wg := sync.WaitGroup{}
wg.Add(20) var rwMutex sync.RWMutex Data := 0
for i:=0;i<10;i++{ go func(i int) { rwMutex.RLock() defer rwMutex.RUnlock() fmt.Printf("读数据: %v , %d\n",Data,i ) wg.Done() time.Sleep(time.Second) }(i)
go func(t int) { rwMutex.Lock() defer rwMutex.Unlock() Data += t fmt.Printf("写数据: %v , %d\n", Data,i) wg.Done() time.Sleep(10*time.Second) }(i) } wg.Wait() }
|
运行这段代码可以看到,在写锁定的情况下,10s内其他协程都无法进行数据的读取,而当写解锁后,所有的读数据的协程都哦完成了对数据的读取。说直白一点就是:写的时候不可读,读的时候不可写,读的时候大家一起读,写的时候只有我能写。
sync.WaitGroup
WaitGroup用于线程总同步。它等待一组线程集合完成,才会继续向下执行。主线程调用Add()方法来设置等待的协程数量,然后每个协程运行,并在完成后调用Done()方法,同时Wait()方法用来阻塞主线程直到所有协程完成。
下面是一段代码实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package main
import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup wg.Add(10) for i:=0;i<10;i++{ go func(t int) { defer wg.Done() fmt.Println(t) }(i) } wg.Wait() }
|
当调用Wg.Add(5)是,程序只会输出5个结果,这是因为5个协程执行了Wg.Done()后,满足了主线程的Wg.Wait()的要求,程序解除阻塞,自动退出。