Golang——常用库sync

news/2025/1/20 15:51:33/

本文详细介绍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 变体)
      • 总结

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. 自旋锁:自定义实现,适合锁时间极短的场景。

http://www.ppmy.cn/news/1564686.html

相关文章

2024最新版JavaScript逆向爬虫教程-------基础篇之Chrome开发者工具学习

目录 一、打开Chrome DevTools的三种方式二、Elements元素面板三、Console控制台面板四、Sources面板五、Network面板六、Application面板七、逆向调试技巧 7.1 善用搜索7.2 查看请求调用堆栈7.3 XHR 请求断点7.4 Console 插桩7.5 堆内存函数调用7.6 复制Console面板输出 工…

Java合并多个List集合的方法

1. 使用addAll()方法 方法说明 Java的java.util.List接口提供了addAll()方法&#xff0c;可以直接将一个集合中的所有元素添加到另一个集合中。 示例代码 import java.util.ArrayList; import java.util.List;public class ListMergeExample {public static void main(Stri…

Go 切片:用法和本质

要想更好的了解一个知识点&#xff0c;实战是最好的经历。 题目 我这里放一道题目&#xff1a; package mainimport "fmt"func SliceRise(s []int) {s append(s, 0)for i : range s {s[i]}fmt.Println(s) }func SlicePrint() {s1 : []int{1, 2}s2 : s1s2 append…

搜维尔科技提供完整的人形机器人解决方案以及训练系统

问题&#xff1a;从灵巧手收集的数据是否也会在大脑大模型中训练&#xff0c;或是在专门用于手部控制的单独模型中训练&#xff1f; Q: If the data collected from dexterous hands will be trained as well in the brain large model, or in a separate model dedicated for…

mysql 系统学习1

Linux C/C 操作MySQL - Henkk - 博客园

基于SSM的家庭记账本小程序设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

云原生前端开发:打造现代化高性能的用户体验

引言&#xff1a;前端开发的新风向 在过去的几年中&#xff0c;前端开发领域经历了快速的演变&#xff0c;从早期的静态网页到如今复杂的单页应用&#xff08;SPA&#xff09;&#xff0c;再到微前端架构和渐进式Web应用&#xff08;PWA&#xff09;&#xff0c;前端技术一直处…

用 Python 从零开始创建神经网络(二十二):预测(Prediction)/推理(Inference)(完结)

预测&#xff08;Prediction&#xff09;/推理&#xff08;Inference&#xff09;&#xff08;完结&#xff09; 引言完整代码&#xff1a; 引言 虽然我们经常将大部分时间花在训练和测试模型上&#xff0c;但我们这样做的核心原因是希望有一个能够接受新输入并生成期望输出的…