Golang笔记——常用库sync

devtools/2025/1/19 5:05:30/

大家好,这里是Good Note,关注 公主号:Goodnote,专栏文章私信限时Free。本文详细介绍Golang的常用库sync,提供了一系列工具来处理 并发编程 中的同步问题。

在这里插入图片描述

文章目录

    • sync
      • 1. `sync.Mutex` - 互斥锁
      • 2. `sync.RWMutex` - 读写锁
      • 3. `sync.WaitGroup` - 等待组
      • 4. `sync.Once` - 单次执行
      • 5. `sync.Cond` - 条件变量
      • 6. `sync.Pool` - 对象复用池
      • 7. sync.Map(并发安全的 Map)
      • 8. 原子操作(sync/atomic)
      • 9. 信号量(基于 channel 实现)
      • 10. 自旋锁(sync.Mutex 变体)
      • 总结
    • 历史文章
      • MySQL数据库
      • Redis
      • Golang

sync

sync 是 Go 标准库中的并发同步包,提供了一系列工具来处理 并发编程 中的同步问题。sync 包主要用于管理多个 goroutine 之间的共享资源访问,确保程序的正确性和线程安全。

1. sync.Mutex - 互斥锁

  • 作用:用于保护共享资源,保证同一时刻只有一个 goroutine 能访问临界区代码,避免竞态条件(Race Condition)。
  • 两个方法
    • Lock():获取锁,如果锁已经被其他 goroutine 获取,当前 goroutine 会阻塞。
    • Unlock():释放锁。

示例代码

package mainimport ("fmt""sync"
)var counter int
var mutex sync.Mutexfunc worker(wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 1000; i++ {mutex.Lock()   // 获取锁counter++      // 临界区:访问共享变量mutex.Unlock() // 释放锁}
}func main() {var wg sync.WaitGroupfor 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():获取/释放写锁。

示例代码

package mainimport ("fmt""sync""time"
)var data int
var rwMutex sync.RWMutexfunc 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 += 1fmt.Printf("Writer %d: Wrote data %d\n", id, data)time.Sleep(time.Millisecond * 10)rwMutex.Unlock()
}func main() {var wg sync.WaitGroupfor 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。

示例代码

package mainimport ("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.WaitGroupfor 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 调用。

示例代码

package mainimport ("fmt""sync"
)var once sync.Oncefunc 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.WaitGroupfor 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。

示例代码

package mainimport ("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.WaitGroupfor i := 1; i <= 3; i++ {wg.Add(1)go worker(i, &wg)}time.Sleep(time.Second)cond.L.Lock()ready = truecond.Broadcast() // 唤醒所有等待的 goroutinecond.L.Unlock()wg.Wait()
}

说明

  • sync.Cond 用于等待条件满足。
  • Signal() 唤醒一个等待 goroutine,Broadcast() 唤醒所有等待的 goroutine。

6. sync.Pool - 对象复用池

  • 作用:用于缓存临时对象,减少内存分配次数,提升性能。
  • 常用方法
    • Get():获取对象。如果存储了多个对象,Get 获取哪个对象是 不确定的,没有特定的顺序。
    • Put():放回对象。

示例代码

package mainimport ("fmt""sync"
)var pool = sync.Pool{New: func() interface{} {return "new object"},
}func main() {obj := pool.Get()fmt.Println(obj) // 输出:new objectpool.Put("reused object")fmt.Println(pool.Get()) // 输出:reused object
}

说明

  • sync.Pool 可以复用对象,减少垃圾回收压力。
  • 如果池中没有对象,会调用 New 创建新对象 new object

7. sync.Map(并发安全的 Map)

作用:

  • sync.Map 是 Go 1.9 引入的并发安全的 Map,用于多协程环境下读写共享数据。
  • 相比普通的 mapsync.Map 内部实现了同步机制,避免了竞态条件。

特点:

  • 性能优化:适用于读取多、写入少的场景。
  • 并发安全:支持多协程同时读写。
  • 与普通 map 区别:不需要显式加锁,sync.Map 提供了专门的方法。

方法:

  • Store(key, value):存储键值对。
  • Load(key):获取对应的值。
  • Delete(key):删除键值对。
  • Range(f func(key, value)):遍历 Map 中的所有键值对。
  • LoadOrStore(key, value):如果键已存在,返回对应的值;不存在则存储新值。

示例代码:

package mainimport ("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:交换值。

示例代码:

package mainimport ("fmt""sync""sync/atomic"
)func main() {var counter int32 = 0var wg sync.WaitGroupfor 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 实现类似信号量的机制,限制并发协程的数量。

示例代码:

package mainimport ("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.WaitGroupsem := 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 时间片,适用于锁占用时间非常短的场景。

示例实现

package mainimport ("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 SpinLockvar counter intvar wg sync.WaitGroupfor 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 包提供了多种并发控制机制,主要包括:

  1. 互斥锁sync.Mutexsync.RWMutex
  2. 等待组sync.WaitGroup
  3. 单次执行sync.Once
  4. 条件变量sync.Cond
  5. 对象池sync.Pool
  6. 并发安全的 Mapsync.Map
  7. 原子操作sync/atomic
  8. 信号量:基于 channel 实现。
  9. 自旋锁:自定义实现,适合锁时间极短的场景。

历史文章

MySQL数据库

MySQL数据库

Redis

Redis数据库笔记合集

Golang

  1. Golang笔记——语言基础知识
  2. Golang笔记——切片与数组
  3. Golang笔记——hashmap
  4. Golang笔记——rune和byte
  5. Golang笔记——channel
  6. Golang笔记——Interface类型
  7. Golang笔记——数组、Slice、Map、Channel的并发安全性
  8. Golang笔记——协程同步
  9. Golang笔记——并发控制
  10. Golang笔记——GPM调度器
  11. Golang笔记—— new() 、 make() 和简短声明符
  12. Golang笔记—— error 和 panic
  13. Golang——内存(内存管理、内存逃逸、垃圾回收 (GC) 机制)
  14. Golang笔记——包的循环引用问题(import cycle not allowed)和匿名导入

http://www.ppmy.cn/devtools/151747.html

相关文章

MySQL程序之:使用命令选项连接到服务器

本节介绍如何使用命令行选项来指定如何为mysql或mysqldump等客户端建立到MySQL服务器的连接。有关使用类似URI的连接字符串或键值对建立连接的信息&#xff0c;请参阅“使用类似URI的字符串或键值对连接到服务器”。有关无法连接的其他信息&#xff0c;请参阅“解决连接到MySQL…

Spring Boot + MyBatis-Flex 配置 ProxySQL 的完整指南

✅ Spring Boot MyBatis-Flex 配置 ProxySQL 的完整指南 下面是一个详细的教程&#xff0c;指导您如何在 Spring Boot 项目中使用 MyBatis-Flex 配置 ProxySQL 进行 读写分离 和 主从同步 的数据库访问。 &#x1f3af; 目标 在 Spring Boot 中连接 ProxySQL。使用 MyBatis-…

excel 判断某个单元格的日期,如果超过3天,则在另一个单元格显示超过三天的公式

excel 判断某个单元格的日期&#xff0c;如果超过3天&#xff0c;则在另一个单元格显示超过三天的公式&#xff0c;公式如下&#xff1a; IF(DATEDIF(C627,TODAY(),"d")<4,"3天以内","超过三天") IF(D627"超过3天","文件赶紧…

HarmonyOS NEXT应用开发边学边玩系列:从零实现一影视APP (三、影视搜索页功能实现)

在HarmonyOS NEXT开发环境中&#xff0c;可以使用nutpi/axios库来简化网络请求的操作。本文将展示如何使用HarmonyOS NEXT框架和nutpi/axios库&#xff0c;从零开始实现一个简单的影视APP&#xff0c;主要关注影视搜索页的功能实现。 为什么选择nutpi/axios&#xff1f; nutpi…

【深度学习】神经网络灾难性遗忘(Catastrophic Forgetting,CF)问题

文章目录 1. 什么是灾难性遗忘&#xff1f;2. 为什么会存在灾难性遗忘&#xff1f;2.1 网络权重的更新2.2 没有有效的记忆机制2.3 任务间数据分布差异 3. 目前解决方案3.1 弹性权重保持&#xff08;Elastic Weight Consolidation, EWC&#xff09;3.2 其他方法 1. 什么是灾难性…

【专题系列】华为堆叠场景-堆叠无法组建、堆叠异常分裂和堆叠快速升级异常处理

互联网各领域资料分享专区(不定期更新)&#xff1a; Sheet 堆叠无法组建 1、堆叠无法组建&#xff0c;首先排查两侧配置是否正确&#xff0c;主要为 DOMAIN、堆叠端口是否正确。通过 display stack configuration all 来查看。display stack troubleshooting 也能显示常见的配…

2019-腾讯Android面试精选题——谈一谈Binder的原理和实现一次拷贝的流程

####2.2 Linux 下的传统 IPC 通信原理 理解了上面的几个概念&#xff0c;我们再来看看传统的 IPC 方式中&#xff0c;进程之间是如何实现通信的。 通常的做法是消息发送方将要发送的数据存放在内存缓存区中&#xff0c;通过系统调用进入内核态。然后内核程序在内核空间分配内…

重拾Python学习,先从把python删除开始。。。

自己折腾就是不行啊&#xff0c;屡战屡败&#xff0c;最近终于找到前辈教我 第一步 删除Python 先把前阵子折腾的WSL和VScode删掉。还是得用spyder&#xff0c;跟matlab最像&#xff0c;也最容易入手。 从VScode上搞python&#xff0c;最后安装到appdata上&#xff0c;安装插…