概述
对于任一共享资源,同一时间保障只有一个操作者,这种办法称为 互斥机制
。
关键字 Mutex
示意互斥锁类型,它的 Lock
办法用于获取锁,Unlock
办法用于开释锁。
在 Lock
和 Unlock
之间的代码,能够读取和批改共享资源,这部分区域称为 临界区
。
谬误的并发操作
先来看一个谬误的示例。
在 Map 大节中讲到, Map
不是并发平安的, 也就是说,如果在多个线程中,同时对一个 Map 进行读写,会报错。
当初来验证一下, 通过启动 100 个 goroutine
来模仿并发调用,每个 goroutine 都对 Map 的 key 进行设置。
package mainimport "sync"func main() { m := make(map[int]bool) var wg sync.WaitGroup for j := 0; j < 100; j++ { wg.Add(1) go func(key int) { defer func() { wg.Done() }() m[key] = true // 对 Map 进行并发写入 }(j) } wg.Wait()}// $ go run main.go// 输入如下,报错信息/** fatal error: concurrent map writes fatal error: concurrent map writes goroutine 104 [running]: main.main.func1(0x0?) /home/codes/Go-examples-for-beginners/main.go:18 +0x66 created by main.main /home/codes/Go-examples-for-beginners/main.go:13 +0x45 goroutine 1 [semacquire]: sync.runtime_Semacquire(0xc0000112c0?) /usr/local/go/src/runtime/sema.go:62 +0x25 sync.(*WaitGroup).Wait(0x60?) /usr/local/go/src/sync/waitgroup.go:139 +0x52 main.main() /home/codes/Go-examples-for-beginners/main.go:22 +0x105 ... ... ...*/
通过输入信息 fatal error: concurrent map writes
能够看到,并发写入 Map 的确会报错。
正确的并发操作
Map 并发写入如何正确地实现呢?
一种简略的计划是在并发临界区域 (也就是设置 Map key 的中央) 进行加互斥锁操作, 互斥锁保障了同一时刻只有一个 goroutine 取得锁,其余 goroutine 全副处于期待状态,这样就把并发写入变成了串行写入,从而打消了报错问题。
package mainimport ( "fmt" "sync")func main() { var mu sync.Mutex m := make(map[int]bool) var wg sync.WaitGroup for j := 0; j < 100; j++ { wg.Add(1) go func(key int) { defer func() { wg.Done() }() mu.Lock() // 写入前加锁 m[key] = true // 对 Map 进行并发写入 mu.Unlock() // 写入实现解锁 }(j) } wg.Wait() fmt.Printf("Map size = %d\n", len(m))}// $ go run main.go// 输入如下/** Map size = 100*/
扩大浏览
- 互斥锁 - 维基百科
- 临界区 - 百度百科