文章参考go语言圣经,并进行了整理,希望可以帮到工作中有需要的小伙伴~
在一个线性(只有一个goroutine)程序中,程序的执行顺序由程序的逻辑来决定。在有两个或者更多goroutine的程序中,每一个goroutine内的语句也是按照顺序去执行的,但是没法知道不同goroutine中事件的执行顺序,当无法确认一个事件是在另一个事件前面或后面发生的时候,说明这些事件是并发的
1、sync.Mutex互斥锁
在并发模型中,可以用互斥锁来处理并发问题,锁本身可以认为是一个共享变量,一个线程加了互斥锁后,其他线程只能等待,直到锁被释放才能获取新锁。在go语言中sync包里的Mutex类型直接支持互斥锁,Lock方法可以获取锁,Unlock方法会释放锁
// 示例
var (
balance int
mu sync.Mutex
wg sync.WaitGroup
)
func Deposit(amount int) {
mu.Lock() // 加锁
balance = balance + amount
fmt.Println("存入金额:" + strconv.Itoa(amount))
mu.Unlock() // 释放锁
// 在goroutine完成任务后,调用wg.Done(),等同于wg.Add(-1)
wg.Done()
}
func Balance() int {
mu.Lock()
// 释放资源
defer mu.Unlock()
return balance
}
func main() {
wg.Add(3)
// 启动3个goroutine
go Deposit(100)
go Deposit(200)
go Deposit(300)
// 阻塞主线程,等待所有goroutine完成调用
// 当所有goroutine都调用完wg.Done()之后才返回
wg.Wait()
fmt.Println(Balance())
}
// 输出
存入金额:300
存入金额:100
存入金额:200
账户总额:60总结1> Mutex 为互斥锁,Lock() 加锁,Unlock() 解锁2> 在一个 goroutine 获得 Mutex 后,其它 goroutine 只能等待这个 goroutine 释放该 Mutex3> 使用 Lock() 加锁后,不能再继续对其加锁,通过 Unlock() 解锁后才能再次加锁4> 在 Lock() 之前使用 Unlock() 会导致 panic 异常5> 在同一个 goroutine 中的 Mutex 解锁之前再次进行加锁,会导致死锁6> 常用于读写不确定,并且只有一个读或者写的场景
// 示例
func main() {
var mutex sync.Mutex // 互斥锁
mutex.Lock()
fmt.Println("Locked")
c := make([]chan int, 4)
for i := 0; i < 3; i++ {
c[i] = make(chan int)
go func(i int, c chan int) {
fmt.Println("Start: ", i)
mutex.Lock()
fmt.Println("Goroutine Locked: ", i)
time.Sleep(time.Second)
fmt.Println("Goroutine Unlock: ", i)
mutex.Unlock()
c <- i
}(i, c[i])
}
time.Sleep(time.Second)
fmt.Println("Unlock")
mutex.Unlock()
time.Sleep(time.Second)
for _, c := range channels {
<-c
}
}
2、sync.RWMutex读写锁
// 示例
var (
balance = 10
//mu sync.Mutex // 互斥锁
mu sync.RWMutex // 读写锁
wg sync.WaitGroup
)
func Deposit() {
if Balance() > 0 {
mu.Lock()
if balance > 0 {
balance--
fmt.Println("当前余额:" + strconv.Itoa(balance))
} else {
fmt.Println("余额不足...")
}
mu.Unlock()
} else {
fmt.Println("余额不足.")
}
wg.Done()
}
func Balance() int {
// 注:可分别通过方式1和方式2,观察程序运行的差异~
// 方式1
// 使用互斥锁(Mutex),读取的时候会阻塞其它goroutine运行
mu.Lock()
defer mu.Unlock()
// 方式2
// 使用读写锁(RWMutex),多个goroutine可以同时读取
//mu.RLock()
//defer mu.RUnlock()
return balance
}
func main() {
for i := 0; i < 20; i++ {
wg.Add(1)
go Deposit()
}
wg.Wait()
}
1> RWMutex 是单写多读锁,该锁可以加多个读锁或者一个写锁
2> 读锁占用的情况下会阻止写,不会阻止读,多个 goroutine 可以同时获取读锁
3> 写锁会阻止其他 goroutine 读写,整个锁由该 goroutine 独占
4> 多用于读多写少的场景
#Lock() 和 Unlock()
在加写锁之前已经有其它的读锁和写锁,则 Lock() 会阻塞直到该锁可用
// 读写锁示例1
func main() {
var mutex sync.RWMutex // 读写锁
mutex.Lock()
fmt.Println("Locked")
c := make([]chan int, 4)
for i := 0; i < 3; i++ {
c[i] = make(chan int)
go func(i int, c chan int) {
fmt.Println("Start: ", i)
mutex.Lock() // Lock()
fmt.Println("Goroutine Locked: ", i)
time.Sleep(time.Second)
fmt.Println("Goroutine Unlock: ", i)
mutex.Unlock() // Unlock()
c <- i
}(i, c[i])
}
time.Sleep(time.Second)
fmt.Println("Unlock")
mutex.Unlock()
time.Sleep(time.Second)
for _, c := range c {
<-c
}
}
// 输出
Locked
Start: 3
Start: 2
Start: 1
Start: 0
Unlock
Goroutine Locked: 3
Goroutine Unlock: 3
Goroutine Locked: 2
Goroutine Unlock: 2
Goroutine Locked: 1
Goroutine Unlock: 1
Goroutine Locked: 0
Goroutine Unlock: 0
#RLock() 和 RUnlock()
RLock() 加读锁时,如果存在写锁,则无法加读锁;当只有读锁或者没有锁时,可以加读锁,读锁可以加多个
// 读写锁示例2
func main() {
var mutex sync.RWMutex // 读写锁
mutex.Lock()
fmt.Println("Locked")
c := make([]chan int, 4)
for i := 0; i < 3; i++ {
c[i] = make(chan int)
go func(i int, c chan int) {
fmt.Println("Start: ", i)
mutex.RLock() // RLock()
fmt.Println("Goroutine Locked: ", i)
fmt.Println("Goroutine Unlock: ", i)
mutex.RUnlock() // RUnlock()
c <- i
}(i, c[i])
}
time.Sleep(time.Second)
fmt.Println("Unlock")
mutex.Unlock()
time.Sleep(time.Second)
for _, c := range c {
<-c
}
}
// 输出
Locked
Start: 0
Start: 2
Start: 3
Start: 1
Unlock
Goroutine Locked: 1
Goroutine Unlock: 1
Goroutine Locked: 2
Goroutine Unlock: 2
Goroutine Locked: 3
Goroutine Locked: 0
Goroutine Unlock: 0
Goroutine Unlock: 3
#有兴趣的小伙伴可以将读写锁示例1和示例2运行一下,对比打印出来数据的差别,可以有助于理解锁之间的差异~