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 main

import (
	"fmt"
	"sync"
	"time"
)

var (
	mu    sync.Mutex
	count 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 int32
	sema  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 main

import (
	"fmt"
	"sync"
	"time"
)

var (
	mu   sync.RWMutex
	data = 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] = value
	time.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的增减追踪协程的完成状态,通过信号量实现阻塞和唤醒。这样能够保证所有协程完成后,主程序才会向下运行。

相关推荐
Zfox_2 小时前
【Go】 协程和 channel
开发语言·后端·golang
a***81392 小时前
【Go】Go语言基础学习(Go安装配置、基础语法)
服务器·学习·golang
k***92162 小时前
【Golang】——Gin 框架中的表单处理与数据绑定
microsoft·golang·gin
黑夜路人2 小时前
Cursor中rules配置参考-202504版(含前后端Golang/TypeScript/Kotlin等)
ide·vscode·ai·golang
g***86692 小时前
Windows上安装Go并配置环境变量(图文步骤)
开发语言·windows·golang
Zfox_2 小时前
【Go】结构体、自定义类型与接口
开发语言·后端·golang
h***01543 小时前
图解缓存淘汰算法 LRU、LFU | 最近最少使用、最不经常使用算法 | go语言实现
算法·缓存·golang
苏琢玉3 小时前
从零开始做 Go 项目:我的目录设计分享
开发语言·后端·golang
ByNotD0g3 小时前
Golang开发项目学习
学习·微服务·云原生·golang