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

相关推荐
会跑的葫芦怪4 小时前
Go语言在区块链开发中的应用场景详解
golang·区块链
秦禹辰4 小时前
开源多场景问答社区论坛Apache Answer本地部署并发布至公网使用
开发语言·后端·golang
数据知道6 小时前
Go基础:常用数学函数处理(主要是math包rand包的处理)
开发语言·后端·golang·go语言
学习同学6 小时前
从0到1制作一个go语言服务器 (一) 配置
服务器·开发语言·golang
数据知道7 小时前
Go基础:文件与文件夹操作详解
开发语言·后端·golang·go语言
gopyer7 小时前
180课时吃透Go语言游戏后端开发2:Go语言中的变量
开发语言·游戏·golang·游戏后端开发
学历真的很重要10 小时前
Claude Code 万字斜杠命令指南
后端·语言模型·面试·职场和发展·golang·ai编程
会跑的葫芦怪12 小时前
Go语言net/http库使用详解
http·golang·iphone
数据知道1 天前
Go基础:Go语言能用到的常用时间处理
开发语言·后端·golang·go语言