Go标准库中,用Mutex实现互斥锁
Mutex是使用最广泛的同步原语(Synchronization primitives),也有人叫并发原语。并发原语都包括互斥锁Mutex、读写锁RWMutex、并发编排WaitGroup、条件变量Cond、Channel等
同步原语的适用场景:
- 资源共享。并发地读写共享资源,会出现数据竞争(data race)的问题,所以需要Mutex、RWMutex来保护
- 任务编排。需要gotoutine按照一定的规律执行,而goroutine之间有相互等待或者依赖的顺序关系,通常用WaitGroup或者Channel来实现
- 消息传递。信息交流以及不同的goroutine之间的线程安全和数据交流,常用Channel来实现
互斥锁的实现机制
临界区:
在并发编程中,如果程序中的一部分会被并发访问或修改,那么,为了避免并发访问导致的意想不到的结果,这部分程序需要被保护起来,这部分被保护起来的程序,就叫做临界区。(其实就是一个被共享的资源)
Mutex的基本使用
Locker接口
Locker接口定义了锁同步原语的方法集
type Locker interface{
Lock()
Unlock()
}
但这个接口在实际中用的不多,一般用具体的同步原语
Lock & Unlock
互斥锁Mutex提供了两个方法Lock和Unlock: 进入临界区前调用Lock方法,推出临界区的时候调用Unlock方法。Lock和Unlock一定要成对出现
func(m *Mutex)Lock()
func(m *Mutex)Unlock()
这里有一点需要注意:Mutex 的零值是还没有 goroutine 等待的未加锁的状态,所以你不需要额外的初始化,直接声明变量(如 var mu sync.Mutex)即可。
很多情况下Mutex会嵌入到其他struct中使用,比如:
type Counter struct{
mu sync.Mutex
Count uint64
}
未加锁的例子
创建10个goroutine,同时不断地对一个变量(count)进行加 1 操作,每个 goroutine 负责执行 10 万次的加 1 操作,我们期望的最后计数的结果是 10 * 100000 = 1000000 (一百万)。
import(
"fmt"
"sync"
)
func main(){
var count = 0
//辅助变量sync.WaitGroup
var wg sync.WaitGroup
//加goroutine
wg.Add()
for i:=0;i<10;i++{
go func(){
defer wg.Done
//count++
for j :=0;j<100000;j++{
count++
}
}()
}
//等待10个goroutine完成
wg.Wait()
fmt.Println(count)
}
结果不会是1000000,因为count++不是原子操作,会出现data race的问题。
加锁
package main
import(
"fmt"
"sync"
)
func main(){
//互斥锁
var mu sync.Mutex
count:=0
//辅助变量, 用于控制所有的goroutine都完成
var wg mu.WaitGroup
wg.Add(10)
for i := 0 ;i <10;i++{
go func(){
for j := 0 ;j < 100000;j++{
mu.Lock()
count++
mu.Unlock()
}
}()
}
wg.Wait()
fmt.Println(count)
}
锁写进 结构体/方法
package main
import(
"fmt"
"sync"
)
type Counter struct{
sync.Mutex
Count int
}
func main(){
var wg sync.WaitGroup
var counter Counter
wg.Add(10)
for i:=0;i<10;i++{
go func(){
defer wg.Done()
for j := 0 ; j<100000;j++{
counter.Lock()
counter.Count++
counter.Unlock()
}
}()
}
wg.Wait()
fmt.Println(counter.count)
}
package main
import(
"fmt"
"sync"
)
type Counter struct{
sync.Mutex
Count int
}
func (counter *Counter) Incr(){
counter.Lock()
counter.Count++
counter.Unlock()
}
func (counter *Counter) GetValue() int{
counter.Locker
defer counter.Unlock
return counter.Count
}
func main(){
var counter Counter
var wg snyc.WaitGroup
wg.Add(10)
for i:=0;1<10;i++{
go func(){
defer wg.Done()
for j :=0;j<100000;j++{
counter.Incr()
}
}()
}
wg.Wait()
fmt.Println(counter.GetValue)
}