Golang学习笔记:标准库sync包
在Go语言中,sync包提供了用于处理并发常使用的多种同步原语,以下是sync中核心,也是常用的组件:
- Sync.Mutex 互斥锁,用于保护临界区,防止多个goroutinue访问同一资源。
- Sync.RWMutex 读写锁,允许多个协程读取共享资源,而写独占。
- sync.WaitGroup 等待一组协程执行完成。
- sync.Map 一个并发安全的map,适合map的并发读写场景。 参考
- sync.Pool 对象池,管理重复使用的对象,减少内存分配和垃圾回收压力。参考
- sync.Cond 条件变量,用于在协程之间协调事件发生的顺序。参考
- sync.Once 用于单例对象的创建。参考
Sync.Mutex
互斥锁,用于在并发环境中多个协程修改共享数据,通过Lock获取锁,Unlock释放锁。
package mainimport ("fmt""sync""time"
)var (mu sync.Mutexcount int
)func main() {wg := new(sync.WaitGroup)for i := 1; i <= 10; i++ {wg.Add(1)go func(i int) {defer wg.Done()increment()}(i)}wg.Wait()fmt.Println("count: ", count)
}func increment() {for i := 1; i <= 5; i++ {mu.Lock()count++mu.Unlock()time.Sleep(100 * time.Millisecond)}
}
实现原理
参考
sync.Mutex的结构体:
type Mutex struct {state int32sema uint32
}
state表示当前锁的状态,sema用于控制锁状态的信号量,当持有该锁的协程释放锁后,通过sema来唤醒阻塞等待获取锁的协程。
互斥锁的两种模式
Mutex有两种模式,正常模式和饥饿模式,
正常模式下,所有阻塞在等待队列中的goroutine会按顺序进行锁获取,当唤醒一个等待队列中的goroutine时,此goroutine并不会直接获取到锁,而是会和新请求锁的goroutine竞争。 通常新请求锁的goroutine更容易获取锁,这是因为新请求锁的goroutine正在占用cpu片执行,大概率可以直接执行到获取到锁的逻辑。
饥饿模式下, 新请求锁的goroutine不会进行锁获取,而是加入到队列尾部阻塞等待获取锁。
饥饿模式的触发条件:
- 当一个goroutine等待锁的时间超过1ms时,互斥锁会切换到饥饿模式.
饥饿模式的取消条件:
- 当获取到锁的这个goroutine是等待锁队列中的最后一个goroutine,互斥锁会切换到正常模式
- 当获取到锁的这个goroutine的等待时间在1ms之内,互斥锁会切换到正常模式
总结
sync.Mutex通过state表示锁的状态,但锁被释放后,通过信号量唤醒阻塞等待获取锁的协程。另外锁有两种模式:正常模式和饥饿模式。
sync.RWMutex
读写锁,是互斥锁的一个改进版,允许多个读者同时访问数据但只允许一个写者,用RLock获取读锁,RUnlock释放读锁;Lock和Unlock分别获取锁和释放锁;
package mainimport ("fmt""sync""time"
)var (mu sync.RWMutexdata = make(map[string]string)
)func main() {wg := new(sync.WaitGroup)for i := 0; i < 4; i++ {wg.Add(1)go func(i int) {defer wg.Done()readData(fmt.Sprintf("key-%d", i))}(i)}go func() {writeData("testKey", "testValue")}()wg.Wait()fmt.Printf("final data: %+v\n", data)
}func readData(key string) {mu.RLock()defer mu.RUnlock()fmt.Printf("read cache key: %s; value: %s\n", key, data[key])time.Sleep(100 * time.Millisecond)
}func writeData(key string, value string) {mu.Lock()defer mu.Unlock()data[key] = valuetime.Sleep(100 * time.Millisecond)
}
原理
偷个懒 参考
总结
sync.RWMutex是互斥锁的一个改进版,试用于例如缓存、配置信息等读多写少的场景中。特点是:读写互阻塞,读读不阻塞。
sync.WaitGroup
sync.WaitGroup是go标准库sync提供的同步原语,用于等待一组协程执行完成。他的主要作用是等待所有协程执行完成之后做后续动作,避免主程序过早退出。
源码解读
type WaitGroup struct {noCopy noCopy //避免复制state atomic.Uint64 // high 32 bits are counter, low 32 bits are waiter count. 高32位表示counter的数据(及未完成的协程数量),低32位表示等待者(waiter)的数量sema uint32 //信号量, 用于阻塞 / 唤醒 waiter
}
使用方法
-
Add方法
Add方法的主要作用是管理计数器counter,并在counter为0时,唤醒waiter。 -
Done方法
Done方法主要作用是标记一个协程已经执行完成, 减少counter值。 -
Wait方法
Wait方法用于等待counter的值为0,当counter为0时会被runtime_Semrelease唤醒,执行后续操作。
总结
sync.WaitGroup是通过counter的增减追踪协程的完成状态,通过信号量实现阻塞和唤醒。这样能够保证所有协程完成后,主程序才会向下运行。