Golang——常用库sync

本文详细介绍Golang的常用库sync,提供了一系列工具来处理 并发编程 中的同步问题。

文章目录

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

sync

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

1. sync.Mutex - 互斥锁

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

示例代码

go 复制代码
package main

import (
	"fmt"
	"sync"
)

var counter int
var mutex sync.Mutex

func worker(wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 0; i < 1000; i++ {
		mutex.Lock()   // 获取锁
		counter++      // 临界区:访问共享变量
		mutex.Unlock() // 释放锁
	}
}

func main() {
	var wg sync.WaitGroup
	for 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():获取/释放写锁。

示例代码

go 复制代码
package main

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

var data int
var rwMutex sync.RWMutex

func 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 += 1
	fmt.Printf("Writer %d: Wrote data %d\n", id, data)
	time.Sleep(time.Millisecond * 10)
	rwMutex.Unlock()
}

func main() {
	var wg sync.WaitGroup
	for 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。

示例代码

go 复制代码
package main

import (
	"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.WaitGroup

	for 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 调用。

示例代码

go 复制代码
package main

import (
	"fmt"
	"sync"
)

var once sync.Once

func 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.WaitGroup
	for 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。

示例代码

go 复制代码
package main

import (
	"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.WaitGroup
	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go worker(i, &wg)
	}

	time.Sleep(time.Second)
	cond.L.Lock()
	ready = true
	cond.Broadcast() // 唤醒所有等待的 goroutine
	cond.L.Unlock()

	wg.Wait()
}

说明

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

6. sync.Pool - 对象复用池

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

示例代码

go 复制代码
package main

import (
	"fmt"
	"sync"
)

var pool = sync.Pool{
	New: func() interface{} {
		return "new object"
	},
}

func main() {
	obj := pool.Get()
	fmt.Println(obj) // 输出:new object

	pool.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):如果键已存在,返回对应的值;不存在则存储新值。

示例代码:

go 复制代码
package main

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

示例代码:

go 复制代码
package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	var counter int32 = 0
	var wg sync.WaitGroup

	for 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 实现类似信号量的机制,限制并发协程的数量。

示例代码:

go 复制代码
package main

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

示例实现

go 复制代码
package main

import (
	"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 SpinLock
	var counter int
	var wg sync.WaitGroup

	for 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. 自旋锁:自定义实现,适合锁时间极短的场景。
相关推荐
你回到了你的家7 分钟前
4 常量和C预处理器
c语言·开发语言·c++
_.Switch9 分钟前
高级Python Web开发:FastAPI前后端通信与跨域资源共享(CORS)实现详解
开发语言·前端·数据库·后端·python·中间件·fastapi
机器视觉知识推荐、就业指导21 分钟前
Qt/C++ 基于 QGraphicsView 的绘图软件 (附源码下载链接)
开发语言·c++·qt
肉三1 小时前
思科 Java 开发人员面试记录 2024(Java、Spring-Boot、Hibernate)
java·开发语言·数据库·面试·面试题
Thomas_YXQ1 小时前
Unity3D BEPUphysicsint定点数3D物理引擎详解
开发语言·3d·unity·unity3d·游戏开发·热更新
z千鑫1 小时前
【Python】Python之locust压测教程+从0到1demo:基础轻量级压测实战(1)
开发语言·python
黑口罩1 小时前
【JAVA 基础 第(19)课】Hashtable 类用法和注意细节,是Map接口的实现类
java·开发语言
黑不拉几的小白兔1 小时前
Python爬虫学习前传 —— Python从安装到学会一站式服务
爬虫·python·学习
Code侠客行1 小时前
Swift语言的多线程编程
开发语言·后端·golang
Code侠客行1 小时前
Swift语言的软件开发工具
开发语言·后端·golang