概述
sync.Mutex和sync.RWMutex是Go语言底层基础对象,用于构建多个goroutine间的同步逻辑,当多个协程需要对共享数据读写时用到。具体实现极为简洁,性能也有保证。
使用场景
举例:1.多个协程操作同一个文件 2.生产者消费者模型
具体实例我就以最简单的打印方式说明
代码
互斥锁
func print(t *testing.T , i int, wg *sync.WaitGroup , mutex *sync.Mutex ) {
// mutex.Lock ()
t.Logf ("routine i=%d start!" , i)
time.Sleep (time.Millisecond * 10 )
t.Logf ("routine i=%d end!" , i)
wg.Done ()
// mutex.Unlock ()
}
func TestSync(t *testing.T ) {
runtime.GOMAXPROCS (runtime.NumCPU ())
var wg = new(sync.WaitGroup )
var mutex = &sync.Mutex {}
for i := 0
wg.Add (1 )
go print(t, i, wg, mutex)
}
wg.Wait ()
}
这是go的测试用例,并没有用main函数来执行,go的源码里面很多都是以这种形式写的测试用例。
这里需要说明一下,go的协程需要达到真正的并发,需要加上runtime.GOMAXPROCS (runtime.NumCPU ()),
print函数里面mutex.Lock ()注释了,打印的结果是
sync_test.go :12 : routine i=0 start!
sync_test.go :12 : routine i=1 start!
sync_test.go :14 : routine i=1 end!
sync_test.go :14 : routine i=0 end!
协程0 (暂且这么称呼)先执行print函数,但并没有先结束,我们看到协程1 是先结束的,这个程序就有并发安全性的问题。如果需要解决这个问题,达到谁先进入公共代码区域,谁就先结束,只需要把print函数里面互斥锁Lock()和Unlock()打开即可,会看到如下打印信息。
sync_test.go :12 : routine i=0 start!
sync_test.go :14 : routine i=0 end!
sync_test.go :12 : routine i=1 start!
sync_test.go :14 : routine i=1 end!
读写锁
验证结论:如果一个协程在读,其他协程不可以写,其他协程可以读。如果一个协程在写,任何协程都不可以读和写
首先验证多个读
func TestReadLock(t *testing.T) {
lock := new(sync.RWMutex)
go Read (lock , 1 ) //多个协程随便读,并不锁住
go Read (lock , 2 )
time .Sleep(time .Second * 4 )
}
func Read (lock *sync.RWMutex, i int ) {
println(i, "read start" )
lock .RLock()//读锁定
defer lock .RUnlock()//读解锁
println(i, "reading" )
if i == 2 {
time .Sleep(3 * time .Second )
} else {
time .Sleep(1 * time .Second )
}
println(i, "read end" )
}
打印信息如下:
1 read start
2 read start
1 reading
2 reading
1 read end
2 read end
我们可以看出,协程1 在没有read 完之前,协程2 还是可以读的,即验证了可以有多个读
这次来验证第二个结论,有一个协程在读,另个协程能不能写呢?
func TestWriteLock(t *testing.T) {
lock := new(sync.RWMutex)
go Read (lock , 2 )
go Read (lock , 4 )
// time .Sleep(1 * time .Second )
go Write (lock , 1 ) //如果在读,不可以写,可以读,如果在写,不可以写,不可以读
go Write (lock , 3 )
time .Sleep(10 * time .Second )
}
func Read (lock *sync.RWMutex, i int ) {
println(i, "read start" )
lock .RLock()
defer lock .RUnlock()
println(i, "reading" )
if i == 2 {
time .Sleep(3 * time .Second )
} else {
time .Sleep(1 * time .Second )
}
println(i, "read end" )
}
func Write (lock *sync.RWMutex, i int ) {
println(i, "write start" )
lock .Lock ()//写锁定
defer lock .Unlock()//写解锁
println(i, "writing" )
time .Sleep(1 * time .Second )
println(i, "write end" )
}
打印如下:
2 read start
2 reading
3 write start
1 write start
4 read start
2 read end
3 writing
3 write end
4 reading
4 read end
1 writing
1 write end
我们看到,协程2 号线进入读,协程3 号写开始,但并没有writing,而是等到协程2 号read end 之后才开始writing,协程2 号在读的时候,协程4 号开始读,但由于协程3 号是在协程2 号之后进入write start ,所以协程2 号read end 后是协程3 号writing,直到结束,协程4 号开始reading
接下来验证最后一个结论
我们把TestWriteLock函数里面的Write 协程放到前面
func TestWriteLock(t *testing.T) {
lock := new(sync.RWMutex)
go Write (lock , 1 ) //如果在读,不可以写,可以读,如果在写,不可以写,不可以读
go Write (lock , 3 )
go Read (lock , 2 )
go Read (lock , 4 )
time .Sleep(10 * time .Second )
}
打印信息如下:
1 write start
1 writing
4 read start
2 read start
3 write start
1 write end
2 reading
4 reading
4 read end
2 read end
3 writing
3 write end
好了,我们已经验证了我们最开始的结论,是不是很简单。