上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
9.3 同步
通道并非用来取代锁,各有不同使用场景。
通道解决高级别逻辑层次并发架构,锁则用来保护低级别局部代码安全。
- 竟态条件:多线程同时读写共享资源(竟态资源)。
- 临界区:读写竟态资源的代码片段。
- 互斥锁:同一时刻,只有一个线程能进入临界区。
- 读写锁:写独占(其他读写均被阻塞),读共享。
- 信号量:允许指定数量线程进入临界区。
- 自旋锁:失败后,以循环积极尝试。(无上下文切换,小粒度)
- 悲观锁:操作前独占锁定。
- 乐观锁:假定无竞争,后置检查。(Lock Free, CAS)
标准库 sync
提供了多种锁,另有原子操作等。
Mutex
:互斥锁。RWMutex
:读写锁。
WaitGroup
:等待一组任务结束。Cond
:单播或广播唤醒其他任务。Once
:确保只调用一次(函数)。
Map
:并发安全字典,(少写多读,数据不重叠)Pool
:对象池。(缓存对象可被回收)
竞争检测
测试阶段,以 -race
编译,注入竞争检查(data race detection)指令。
- 有较大性能损失,避免在基准测试和发布版本中使用。
- 有不确定性,不能保证百分百测出。
- 单元测试有效完整,定期执行竞争检查。
package main import ( "sync" ) func main() { var wg sync.WaitGroup wg.Add(2) x := 0 go func() { defer wg.Done() x++ }() go func() { defer wg.Done() println(x) }() wg.Wait() }
$ go build -race && ./test ================== WARNING: DATA RACE Read at 0x00c0000160d8 by goroutine 7: main.main.func2() /root/go/test/main.go:20 +0x74 Previous write at 0x00c0000160d8 by goroutine 6: main.main.func1() /root/go/test/main.go:15 +0x86 Goroutine 7 (running) created at: main.main() /root/go/test/main.go:18 +0x1d6 Goroutine 6 (finished) created at: main.main() /root/go/test/main.go:13 +0x12e ================== 1 Found 1 data race(s)
互斥锁
文档中标明 “must not be copied”,应避免复制导致锁机制失效。
type data struct { sync.Mutex } // go vet: passes lock by value func (d data) test(s string) { d.Lock() defer d.Unlock() }
控制在最小范围内,及早释放。
// 错误用法 func { m.Lock() defer m.Unlock url := cache["key"] get(url) // 该操作并不需要保护,延长锁占用。 } // 正确用法 func { m.Lock() url := cache["key"] m.Unlock() get(url) }
不支持递归锁。
func main() { var m sync.Mutex m.Lock() { // m.Lock() // ~~~~~~ fatal error: all goroutines are asleep - deadlock! // m.Unlock() } m.Unlock() }
读写锁
某些场合以读写锁替代互斥锁,可提升性能。
package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup var rw sync.RWMutex x := 0 // 1 写 wg.Add(1) go func() { defer wg.Done() for i := 0; i < 5; i++ { rw.Lock() time.Sleep(time.Second) // 模拟长时间操作! now := time.Now().Format("15:04:05") x++ fmt.Printf("[W] %d, %v\n", x, now) rw.Unlock() } }() // n 读 for i := 0; i < 5; i++ { wg.Add(1) go func(id int) { defer wg.Done() for n := 0; n < 5; n++ { rw.RLock() time.Sleep(time.Second) now := time.Now().Format("15:04:05") fmt.Printf(" [R%d] %d, %v\n", id, x, now) rw.RUnlock() } }(i) } wg.Wait() } /* [W] 1, 11:23:17 // 独占 [R4] 1, 11:23:18 // 并发 [R3] 1, 11:23:18 [R1] 1, 11:23:18 [R0] 1, 11:23:18 [R2] 1, 11:23:18 [W] 2, 11:23:19 [R4] 2, 11:23:20 [R3] 2, 11:23:20 [R1] 2, 11:23:20 [R0] 2, 11:23:20 [R2] 2, 11:23:20 */
条件变量
内部以计数器和队列作为单播(signal)和广播(broadcast)依据。
引入外部锁作为竟态资源保护,可与其他逻辑同步。
func main() { var wg sync.WaitGroup cond := sync.NewCond(&sync.Mutex{}) data := make([]int, 0) // 1 写 wg.Add(1) go func() { defer wg.Done() for i := 0; i < 5; i++ { // 保护竟态资源。 cond.L.Lock() data = append(data, i + 100) cond.L.Unlock() // 唤醒一个。 cond.Signal() } // 唤醒所有(剩余)。 // cond.Broadcast() }() // n 读 for i := 0; i < 5; i++ { wg.Add(1) go func(id int) { defer wg.Done() // 锁定竟态资源。 cond.L.Lock() // 循环检查是否符合后续操作条件。 // 如条件不符,则继续等待。 for len(data) == 0 { cond.Wait() } x := data[0] data = data[1:] cond.L.Unlock() println(id, ":", x) }(i) } wg.Wait() }
为什么 Wait
之前必须加锁?除锁定竟态资源外,还与其内部设计有关。
// cond.go func (c *Cond) Wait() { c.checker.check() t := runtime_notifyListAdd(&c.notify) c.L.Unlock() runtime_notifyListWait(&c.notify, t) c.L.Lock() }
单次执行
确保仅执行一次,无论后续是同一函数或不同函数都不行。
func main() { var once sync.Once f1 := func() { println("1") } f2 := func() { println("2") } once.Do(f1) // 以下目标函数不会执行。 once.Do(f1) once.Do(f2) once.Do(f2) } // 1
以内部状态(done)记录第一次执行,与具体什么函数无关。
func main() { var once sync.Once once.Do(func() { println("1") }) once.Do(func() { println("1") }) once.Do(func() { println("2") }) once.Do(func() { println("2") }) } // 1
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论