互斥锁
每个资源都对应于一个可称为 "互斥锁" 的标记,这个标记用来保证在任意时刻,只能有一个协程(线程)访问该资源。其它的协程只能等待。
互斥锁是传统并发编程对共享资源进行访问控制的主要手段,它由标准库sync中的Mutex结构体类型表示。sync.Mutex类型只有两个公开的指针方法,Lock和Unlock。Lock锁定当前的共享资源,Unlock进行解锁。
在使用互斥锁时,一定要注意:对资源操作完成后,一定要解锁,否则会出现流程执行异常,死锁等问题。通常借助defer。锁定后,立即使用defer语句保证互斥锁及时解锁。
var mt sync.Mutex
mt.Lock() // 加锁
... // 共享资源
mt.Unlock() // 解锁
package main
import (
"sync"
"time"
)
import "fmt"
var mt sync.Mutex
func test(str string) {
mt.Lock()
for _, s := range str{
//fmt.Println(s) // 打印的是字节
fmt.Printf("%c",s)
time.Sleep(time.Millisecond * 100)
}
mt.Unlock()
}
func foo(str string) {
test(str)
}
func fun(str string) {
test(str)
}
func main() {
go foo("hello")
go fun("world")
for {
;
}
}
读写锁
互斥锁的本质是当一个goroutine访问的时候,其他goroutine都不能访问。这样在资源同步,避免竞争的同时也降低了程序的并发性能。程序由原来的并行执行变成了串行执行
当我们对一个不会变化的数据只做“读”操作的话,是不存在资源竞争的问题的,因为读不会对数据进行更改,问题不是出在“读”上,主要是修改,也就是“写”。修改的数据要同步,这样其他goroutine才可以感知到。所以真正的互斥应该是读取和修改、修改和修改之间,读和读是没有互斥操作的必要的。
因此,衍生出另外一种锁,叫做读写锁。
读写锁可以让多个读操作并发,同时读取,但是对于写操作是完全互斥的。也就是说,当一个goroutine进行写操作的时候,其他goroutine既不能进行读操作,也不能进行写操作。
GO中的读写锁由结构体类型sync.RWMutex表示。此类型的方法集合中包含两对方法:
一组是对写操作的锁定和解锁,简称“写锁定”和“写解锁”:
func (*RWMutex)Lock()
func (*RWMutex)Unlock()
另一组表示对读操作的锁定和解锁,简称为“读锁定”与“读解锁”:
func (*RWMutex)RLock()
func (*RWMutex)RUlock()
案例:
var rwMut sync.RWMutex
var value int
func readGo(i int) {
for{
rwMut.RLock() // 加读锁
num:= value
fmt.Printf("%d th , read %d \n", i, num)
rwMut.RUnlock() // 解读锁
}
}
func writeGo(i int, qu chan <- int) {
for q := 1;q < 500; q++{
if q == 10{
qu <- 1
}
num := rand.Intn(1000)
rwMut.Lock() // 加写锁
value = num
fmt.Printf("%d th , write %d \n", i, num)
time.Sleep(time.Millisecond * 100)
rwMut.Unlock() // 解写锁
}
}
func main() {
quit := make(chan int)
rand.Seed(time.Now().UnixNano())
for i:=1;i < 5 ;i++ {
go readGo(i+1)
}
for i:=1;i < 5 ;i++ {
go writeGo(i+1, quit)
}
<- quit
}
>>> :
3 th , write 466 // 写独占
4 th , read 466 // 读共享
2 th , read 466
3 th , read 466
5 th , read 466
4 th , write 646
4 th , read 646
3 th , read 646
2 th , read 646
5 th , read 646
5 th , write 319
....