本文详细介绍Golang的常用库sync,提供了一系列工具来处理 并发编程 中的同步问题。
文章目录
- sync
- [1. `sync.Mutex` - 互斥锁](#1.
sync.Mutex
- 互斥锁)- [2. `sync.RWMutex` - 读写锁](#2.
sync.RWMutex
- 读写锁)- [3. `sync.WaitGroup` - 等待组](#3.
sync.WaitGroup
- 等待组)- [4. `sync.Once` - 单次执行](#4.
sync.Once
- 单次执行)- [5. `sync.Cond` - 条件变量](#5.
sync.Cond
- 条件变量)- [6. `sync.Pool` - 对象复用池](#6.
sync.Pool
- 对象复用池)- [7. sync.Map(并发安全的 Map)](#7. sync.Map(并发安全的 Map))
- [8. 原子操作(sync/atomic)](#8. 原子操作(sync/atomic))
- [9. 信号量(基于 channel 实现)](#9. 信号量(基于 channel 实现))
- [10. 自旋锁(sync.Mutex 变体)](#10. 自旋锁(sync.Mutex 变体))
- 总结
sync
sync
是 Go 标准库中的并发同步包,提供了一系列工具来处理 并发编程 中的同步问题。sync
包主要用于管理多个 goroutine 之间的共享资源访问,确保程序的正确性和线程安全。
1. sync.Mutex
- 互斥锁
- 作用:用于保护共享资源,保证同一时刻只有一个 goroutine 能访问临界区代码,避免竞态条件(Race Condition)。
- 两个方法 :
Lock()
:获取锁,如果锁已经被其他 goroutine 获取,当前 goroutine 会阻塞。Unlock()
:释放锁。
示例代码:
go
package main
import (
"fmt"
"sync"
)
var counter int
var mutex sync.Mutex
func worker(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 1000; i++ {
mutex.Lock() // 获取锁
counter++ // 临界区:访问共享变量
mutex.Unlock() // 释放锁
}
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go worker(&wg)
}
wg.Wait()
fmt.Println("Counter:", counter) // 输出:Counter: 5000
}
说明:
- 使用
sync.Mutex
锁定共享资源访问。 counter
在多个 goroutine 并发执行时是安全的,最终输出结果为 5000。
2. sync.RWMutex
- 读写锁
- 作用 :区分读和写的操作:
- 多个 goroutine 可以同时读取(并发读)。
- 写操作会独占锁(互斥写),阻塞其他读和写操作。
- 方法 :
RLock()
/RUnlock()
:获取/释放读锁。Lock()
/Unlock()
:获取/释放写锁。
示例代码:
go
package main
import (
"fmt"
"sync"
"time"
)
var data int
var rwMutex sync.RWMutex
func readData(id int, wg *sync.WaitGroup) {
defer wg.Done()
rwMutex.RLock()
fmt.Printf("Reader %d: Read data %d\n", id, data)
time.Sleep(time.Millisecond * 10)
rwMutex.RUnlock()
}
func writeData(id int, wg *sync.WaitGroup) {
defer wg.Done()
rwMutex.Lock()
data += 1
fmt.Printf("Writer %d: Wrote data %d\n", id, data)
time.Sleep(time.Millisecond * 10)
rwMutex.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go writeData(i, &wg) // 写操作
}
for i := 1; i <= 5; i++ {
wg.Add(1)
go readData(i, &wg) // 读操作
}
wg.Wait()
}
说明:
- 读写锁允许多个读操作同时进行,但写操作是独占的,其他读写操作会被阻塞。
3. sync.WaitGroup
- 等待组
- 作用:用于等待一组 goroutine 完成。
- 方法 :
Add(n)
:增加等待的 goroutine 计数。Done()
:完成一个 goroutine 的计数。Wait()
:阻塞,直到计数为 0。
示例代码:
go
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d is working\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers finished")
}
说明:
wg.Add(1)
表示增加一个等待的 goroutine。defer wg.Done()
表示 goroutine 完成时减少计数。wg.Wait()
阻塞主线程,直到所有计数为 0。
4. sync.Once
- 单次执行
- 作用 :确保某个操作(如初始化)只执行一次,常用的 close channel 。
- 方法 :
Do(f func())
:只会执行一次f
,无论有多少个 goroutine 调用。
示例代码:
go
package main
import (
"fmt"
"sync"
)
var once sync.Once
func initialize() {
fmt.Println("Initializing...")
}
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
once.Do(initialize) // 确保 initialize 只执行一次
fmt.Printf("Worker %d is working\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
}
说明:
sync.Once
确保initialize
函数只执行一次,即使有多个 goroutine 调用它。
5. sync.Cond
- 条件变量
- 作用:在 goroutine 间通过条件变量进行信号通信。
- 常用方法 :
Wait()
:等待条件满足。Signal()
:唤醒一个等待的 goroutine。Broadcast()
:唤醒所有等待的 goroutine。
示例代码:
go
package main
import (
"fmt"
"sync"
"time"
)
var ready bool
var cond = sync.NewCond(&sync.Mutex{})
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
cond.L.Lock()
for !ready {
cond.Wait() // 等待条件满足
}
cond.L.Unlock()
fmt.Printf("Worker %d is working\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
time.Sleep(time.Second)
cond.L.Lock()
ready = true
cond.Broadcast() // 唤醒所有等待的 goroutine
cond.L.Unlock()
wg.Wait()
}
说明:
sync.Cond
用于等待条件满足。Signal()
唤醒一个等待 goroutine,Broadcast()
唤醒所有等待的 goroutine。
6. sync.Pool
- 对象复用池
- 作用:用于缓存临时对象,减少内存分配次数,提升性能。
- 常用方法 :
Get()
:获取对象。如果存储了多个对象,Get
获取哪个对象是 不确定的,没有特定的顺序。Put()
:放回对象。
示例代码:
go
package main
import (
"fmt"
"sync"
)
var pool = sync.Pool{
New: func() interface{} {
return "new object"
},
}
func main() {
obj := pool.Get()
fmt.Println(obj) // 输出:new object
pool.Put("reused object")
fmt.Println(pool.Get()) // 输出:reused object
}
说明:
sync.Pool
可以复用对象,减少垃圾回收压力。- 如果池中没有对象,会调用
New
创建新对象new object
。
7. sync.Map(并发安全的 Map)
作用:
sync.Map
是 Go 1.9 引入的并发安全的 Map,用于多协程环境下读写共享数据。- 相比普通的
map
,sync.Map
内部实现了同步机制,避免了竞态条件。
特点:
- 性能优化:适用于读取多、写入少的场景。
- 并发安全:支持多协程同时读写。
- 与普通 map 区别 :不需要显式加锁,
sync.Map
提供了专门的方法。
方法:
Store(key, value)
:存储键值对。Load(key)
:获取对应的值。Delete(key)
:删除键值对。Range(f func(key, value))
:遍历 Map 中的所有键值对。LoadOrStore(key, value)
:如果键已存在,返回对应的值;不存在则存储新值。
示例代码:
go
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
// 存储值
m.Store("key1", "value1")
m.Store("key2", "value2")
// 加载值
if val, ok := m.Load("key1"); ok {
fmt.Println("key1:", val) // 输出: key1: value1
}
// 遍历所有键值对
m.Range(func(key, value interface{}) bool {
fmt.Println(key, ":", value)
return true
})
// 删除键
m.Delete("key1")
if _, ok := m.Load("key1"); !ok {
fmt.Println("key1 已删除")
}
}
使用场景:
- 并发访问共享的键值数据,且读多写少。
- 在高并发情况下替代标准
map
避免手动加锁。
8. 原子操作(sync/atomic)
作用:
sync/atomic
提供了一组底层的原子操作,用于对整数、指针和其他变量进行原子级别的读写操作。- 原子操作是并发安全的,避免了竞态条件,不需要加锁。
常用方法:
AddInt32
/AddInt64
:对整数执行加法操作。LoadInt32
/LoadInt64
:读取整数的值。StoreInt32
/StoreInt64
:设置整数的值。CompareAndSwapInt32
:比较并交换值,CAS 操作。SwapInt32
/SwapInt64
:交换值。
示例代码:
go
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var counter int32 = 0
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 1000; j++ {
atomic.AddInt32(&counter, 1) // 原子加 1
}
}()
}
wg.Wait()
fmt.Println("Counter:", counter) // 输出: Counter: 10000
}
使用场景:
- 在高并发场景下实现简单的计数器或标志位。
- 用于替代互斥锁(
sync.Mutex
)以提升性能。
9. 信号量(基于 channel 实现)
Go 并没有内置的信号量类型,但可以通过 channel
实现类似信号量的机制,限制并发协程的数量。
示例代码:
go
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, sem chan struct{}, wg *sync.WaitGroup) {
defer wg.Done()
// 获取信号量
sem <- struct{}{}
fmt.Printf("Worker %d is working...\n", id)
time.Sleep(time.Second)
// 释放信号量
<-sem
}
func main() {
var wg sync.WaitGroup
sem := make(chan struct{}, 3) // 信号量,最多允许 3 个并发协程
for i := 1; i <= 10; i++ {
wg.Add(1)
go worker(i, sem, &wg)
}
wg.Wait()
fmt.Println("All workers finished")
}
说明:
sem
是一个大小为 3 的 channel,用于控制同时运行的 goroutine 数量。- 当信号量已满,新的协程会阻塞,直到有信号量被释放。
使用场景:
- 控制并发协程的最大数量,避免系统资源耗尽。
10. 自旋锁(sync.Mutex 变体)
Go 官方标准库没有提供自旋锁(Spin Lock),但可以通过自定义实现。自旋锁会在获取锁时不断尝试,不释放 CPU 时间片,适用于锁占用时间非常短的场景。
示例实现:
go
package main
import (
"fmt"
"runtime"
"sync"
"sync/atomic"
)
type SpinLock struct {
flag int32
}
func (sl *SpinLock) Lock() {
for !atomic.CompareAndSwapInt32(&sl.flag, 0, 1) {
runtime.Gosched() // 让出 CPU
}
}
func (sl *SpinLock) Unlock() {
atomic.StoreInt32(&sl.flag, 0)
}
func main() {
var lock SpinLock
var counter int
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
lock.Lock()
counter++
lock.Unlock()
}()
}
wg.Wait()
fmt.Println("Counter:", counter) // 输出:Counter: 5
}
说明:
- 自旋锁会反复检查锁状态,适合锁时间极短的场景。
- 不释放 CPU 时间片可能导致资源浪费。
总结
sync
包提供了多种并发控制机制,主要包括:
- 互斥锁 :
sync.Mutex
和sync.RWMutex
。 - 等待组 :
sync.WaitGroup
。 - 单次执行 :
sync.Once
。 - 条件变量 :
sync.Cond
。 - 对象池 :
sync.Pool
。 - 并发安全的 Map :
sync.Map
。 - 原子操作 :
sync/atomic
。 - 信号量 :基于
channel
实现。 - 自旋锁:自定义实现,适合锁时间极短的场景。