1、Mutex
互斥锁 Mutex、读写锁 RWMutex、并发编排WaitGroup
条件变CondChannel 等同步原语。所以,在这里
我先和你说一下同步原语的适用场景。
-
共享资源。并发地读写共享资源,会出现数据竞争(data race)的问题,所以需要Mutex、RWMutex 这样的并发原语来保护。
-
任务编排。需要 goroutine 按照一定的规律执行,而 goroutine 之间有相互等待或者依赖的顺序关系,我们常常使用 WaitGroup 或者 Channel 来实现。
-
消息传递。信息交流以及不同的 goroutine 之间的线程安全的数据交流,常常使用Channel 来实现。
1.1、mutex的基本顺使用方法
Locker 接口
type Locker interface {
Lock()
Unlock()
}
可以看到,Go 定义的锁接口的方法集很简单,就是请求锁(Lock)和释放锁(Unlock)这两个方法
Mutex 以及后面会介绍的读写锁 RWMutex 都实现了 Locker 接口
简单来说,互斥锁 Mutex 就提供两个方法 Lock 和 Unlock:进入临界区之前调用 Lock方法,退出临界区的时候调用 Unlock 方法:
Mutex:如何解决资源并发访问问题?
当一个 goroutine 通过调用 Lock 方法获得了这个锁的拥有权后, 其它请求锁的
goroutine 就会阻塞在 Lock 方法的调用上,直到锁被释放并且自己获取到了这个锁的拥有权。
package main
/*
mutex 的使用
在这个例子中,我们创建了 10 个 goroutine,同时不断地对一个变量(count)进行加 1
操作,每个 goroutine 负责执行 10 万次的加 1 操作,我们期望的最后计数的结果是 10 *
100000 = 1000000 (一百万)。
*/
import (
"fmt"
"sync"
)
func main() {
var count = 0
// 使用WaitGroup等待10个goroutine完成
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
// 对变量count执行10次加1
for j := 0; j < 100000; j++ {
count++
}
}()
}
// 等待10个goroutine完成
wg.Wait()
fmt.Println(count)
}
sync.WaitGroup 来等待所有的 goroutine 执行完毕后,再输出
最终的结果。sync.WaitGroup 这个同步原语我会在后面的课程中具体介绍,现在你只需
要知道,我们使用它来控制等待一组 goroutine 全部做完任务。
count ++ 作为一个共享数据,被所有goroutine所共用,上面的代码我们很难发现问题所以 我们可以使用go run -race counter.go
我们可以使用这个 go tool complie -race -S counter.go
这个可以直接查看代码前后编译后的结果,可以成功检测出来 data race问题
1.2、mutex的其他用法
切入字段的方式
func main() {
var counter Counter
var wg sync.WaitGroup
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)
}
type Counter struct {
sync.Mutex
Count uint64
}
如果嵌入的 struct 有多个字段,我们一般会把 Mutex 放在要控制的字段上面,然后使用
你还可以把获取锁、释放锁、计数加一的逻辑封装成一个方法,对外不需要暴露锁
等逻辑:
func main() {
// 封装好的计数器
var counter Counter
var wg sync.WaitGroup
wg.Add(10)
// 启动10个goroutine
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
// 执行10万次累加
for j := 0; j < 100000; j++ {
counter.Incr() // 受到锁保护的方法
}
}()
}
wg.Wait()
fmt.Println(counter.Count())
}
// 线程安全的计数器类型
type Counter struct {
CounterType int
Name
string
mu sync.Mutex
count uint64
}
// 加1的方法,内部使用互斥锁保护
func (c *Counter) Incr() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
// 得到计数器的值,也需要锁保护
func (c *Counter) Count() uint64 {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}