Go sync 标准库实战指南:吃透并发同步的核心工具

目录

[一、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.Map:并发安全的 Map](#5. sync.Map:并发安全的 Map)

核心作用

关键方法

实战示例:全局并发缓存

性能对比

[6. sync.Pool:临时对象池](#6. sync.Pool:临时对象池)

核心作用

关键方法

实战示例:复用字节缓冲区

适用场景

[7. sync.Cond:条件变量](#7. sync.Cond:条件变量)

核心作用

关键方法

[实战示例:生产者 - 消费者模型](#实战示例:生产者 - 消费者模型)

避坑要点

[8. sync/atomic:原子操作(附属子包)](#8. sync/atomic:原子操作(附属子包))

核心作用

常用方法

实战示例:原子计数器

适用场景

[三、sync 库使用原则](#三、sync 库使用原则)

四、总结


一、sync 库核心定位

sync库解决的是单个 Go 进程内的协程同步问题,所有组件均基于进程内内存实现,无法跨进程 / 跨机器生效。其核心价值是:以极简的 API 实现并发安全,避免开发者重复造轮子。

二、核心组件全解析

1. sync.Mutex:基础互斥锁

核心作用

保证同一时间只有一个协程进入临界区,是解决共享资源竞争的基础工具。

关键特性
  • 排他锁:加锁后其他协程阻塞,直到解锁;
  • 非可重入:同一协程重复加锁会导致死锁(区别于 Java 的 ReentrantLock);
  • 无超时机制:阻塞时只能等解锁,无法主动退出。
实战示例:保护共享计数器
复制代码
package main

import (
	"fmt"
	"sync"
)

var (
	count int
	lock  sync.Mutex
)

// 累加函数(并发安全)
func add(wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 0; i < 1000; i++ {
		lock.Lock()   // 加锁,进入临界区
		count++       // 共享资源操作(非原子性)
		lock.Unlock() // 解锁,释放临界区
	}
}

func main() {
	var wg sync.WaitGroup
	wg.Add(10) // 等待10个协程

	// 启动10个协程并发累加
	for i := 0; i < 10; i++ {
		go add(&wg)
	}

	wg.Wait()
	fmt.Printf("最终计数:%d(预期10000)\n", count) // 精准输出10000
}
避坑要点
  • 解锁必须在defer中:防止临界区 panic 导致锁泄漏;
  • 最小锁粒度:仅对 "共享资源操作" 加锁,避免锁包裹整个函数。

2. sync.RWMutex:读写分离锁

核心作用

针对 "多读少写" 场景优化,读操作可并发,写操作独占,性能优于 Mutex。

关键特性
  • 读锁(RLock/RUnlock):多个协程可同时获取,不阻塞其他读锁;
  • 写锁(Lock/Unlock):独占锁,阻塞所有读 / 写锁;
  • 读写互斥:写锁未释放时,读锁无法获取;读锁未释放时,写锁阻塞。
实战示例:高并发缓存读写
复制代码
package main

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

var (
	// 全局缓存(key=用户ID,value=用户名)
	userCache = make(map[int64]string)
	// 读写锁保护缓存
	rwLock sync.RWMutex
)

// 读操作:加读锁(并发安全)
func getUserName(userID int64) (string, bool) {
	rwLock.RLock()
	defer rwLock.RUnlock()
	// 模拟读耗时(实际场景可能是复杂计算/缓存读取)
	time.Sleep(10 * time.Millisecond)
	name, ok := userCache[userID]
	return name, ok
}

// 写操作:加写锁(独占)
func setUserName(userID int64, name string) {
	rwLock.Lock()
	defer rwLock.Unlock()
	// 模拟写耗时(实际场景可能是数据库写入)
	time.Sleep(100 * time.Millisecond)
	userCache[userID] = name
}

func main() {
	var wg sync.WaitGroup

	// 100个读协程(高并发读)
	wg.Add(100)
	for i := 0; i < 100; i++ {
		go func(id int64) {
			defer wg.Done()
			name, ok := getUserName(id % 10)
			if ok {
				fmt.Printf("读取用户%d:%s\n", id%10, name)
			}
		}(int64(i))
	}

	// 2个写协程(低频写)
	wg.Add(2)
	go func() {
		defer wg.Done()
		setUserName(1, "张三")
	}()
	go func() {
		defer wg.Done()
		setUserName(2, "李四")
	}()

	wg.Wait()
	fmt.Println("最终缓存:", userCache)
}
性能对比
  • 纯读场景:RWMutex 的 QPS 是 Mutex 的 10 倍以上;
  • 读写混合(读:写 = 10:1):RWMutex 性能约为 Mutex 的 5 倍。

3. sync.WaitGroup:协程等待组

核心作用

等待一组协程全部完成,实现协程间的同步(替代 sleep 等待的不优雅写法)。

关键方法
  • Add(n int):设置需要等待的协程数量;
  • Done():协程完成后调用,数量减 1;
  • Wait():阻塞当前协程,直到等待数量为 0。
实战示例:批量执行任务
复制代码
package main

import (
	"fmt"
	"sync"
)

// 模拟业务任务
func task(id int, wg *sync.WaitGroup) {
	defer wg.Done() // 协程退出前必须调用
	fmt.Printf("任务%d开始执行\n", id)
	// 模拟任务耗时
	for i := 0; i < 100000000; i++ {}
	fmt.Printf("任务%d执行完成\n", id)
}

func main() {
	var wg sync.WaitGroup
	taskNum := 5

	wg.Add(taskNum) // 等待5个协程
	for i := 0; i < taskNum; i++ {
		go task(i, &wg)
	}

	wg.Wait() // 阻塞直到所有任务完成
	fmt.Println("所有任务执行完毕")
}
避坑要点
  • Add()必须在启动协程前调用:避免协程已完成但Add()未执行,导致Wait()提前返回;
  • Done()必须与Add()数量匹配:否则会 panic(Add (5) 但只调用 4 次 Done ())。

4. sync.Once:一次性执行

核心作用

保证某个函数仅执行一次,即使多协程同时调用(典型场景:单例初始化)。

关键方法
  • Do(f func()):传入需单次执行的函数,底层通过原子操作实现,无锁且高效。
实战示例:单例配置初始化
复制代码
package main

import (
	"fmt"
	"sync"
)

// 全局配置单例
type AppConfig struct {
	Port    int
	AppName string
}

var (
	config *AppConfig
	once   sync.Once
)

// 初始化配置(仅执行一次)
func initConfig() {
	fmt.Println("执行配置初始化...")
	config = &AppConfig{
		Port:    8080,
		AppName: "sync-demo",
	}
}

// 获取配置单例
func GetConfig() *AppConfig {
	once.Do(initConfig) // 多协程调用仅执行一次initConfig
	return config
}

func main() {
	var wg sync.WaitGroup
	wg.Add(10)

	// 10个协程同时获取配置
	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			cfg := GetConfig()
			fmt.Printf("协程获取配置:%+v\n", cfg)
		}()
	}

	wg.Wait()
}
适用场景
  • 配置文件加载、数据库连接初始化;
  • 单例模式实现、全局资源的一次性初始化。

5. sync.Map:并发安全的 Map

核心作用

替代 Go 原生 map(非并发安全),专为 "读多写少" 场景优化的并发安全 Map。

关键方法
  • Load(key interface{}) (value interface{}, ok bool):读取值;
  • Store(key, value interface{}):写入值;
  • Delete(key interface{}):删除值;
  • Range(f func(key, value interface{}) bool):遍历所有键值对。
实战示例:全局并发缓存
复制代码
package main

import (
	"fmt"
	"sync"
)

func main() {
	var cache sync.Map
	var wg sync.WaitGroup

	// 10个协程写入数据
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func(id int) {
			defer wg.Done()
			cache.Store(id, fmt.Sprintf("value_%d", id))
		}(i)
	}

	// 10个协程读取数据
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func(id int) {
			defer wg.Done()
			if val, ok := cache.Load(id); ok {
				fmt.Printf("读取key=%d,value=%s\n", id, val)
			}
		}(i)
	}

	wg.Wait()

	// 遍历所有数据
	fmt.Println("遍历缓存:")
	cache.Range(func(key, value interface{}) bool {
		fmt.Printf("key=%v, value=%v\n", key, value)
		return true // 返回true继续遍历,false终止
	})
}
性能对比
  • 读多写少场景:sync.Map 性能优于map+RWMutex
  • 写多场景:建议用map+Mutex(sync.Map 的写操作有额外开销)。

6. sync.Pool:临时对象池

核心作用

复用临时对象,减少 GC(垃圾回收)压力,提升高频创建 / 销毁对象场景的性能。

关键方法
  • Get() interface{}:从池中获取对象(无则调用 New 创建);
  • Put(x interface{}):将对象放回池中;
  • New func() interface{}:池为空时创建新对象的函数。
实战示例:复用字节缓冲区
复制代码
package main

import (
	"bytes"
	"fmt"
	"sync"
)

// 创建字节缓冲区池
var bufPool = sync.Pool{
	New: func() interface{} {
		// 新建缓冲区,初始容量1024
		return &bytes.Buffer{}
	},
}

// 模拟高频创建缓冲区的函数
func processData(data string) string {
	// 从池获取缓冲区
	buf := bufPool.Get().(*bytes.Buffer)
	// 用完放回池(清空内容)
	defer func() {
		buf.Reset()
		bufPool.Put(buf)
	}()

	// 模拟数据处理
	buf.WriteString("processed: ")
	buf.WriteString(data)
	return buf.String()
}

func main() {
	var wg sync.WaitGroup
	wg.Add(1000)

	// 1000个协程并发处理数据
	for i := 0; i < 1000; i++ {
		go func(id int) {
			defer wg.Done()
			result := processData(fmt.Sprintf("data_%d", id))
			fmt.Printf("处理结果:%s\n", result)
		}(i)
	}

	wg.Wait()
	fmt.Println("所有数据处理完成")
}
适用场景
  • 高频创建的临时对象(如字节缓冲区、连接对象、结构体实例);
  • 注意:Pool 中的对象可能被 GC 回收,不能存储需持久化的数据。

7. sync.Cond:条件变量

核心作用

实现协程间的 "等待 - 唤醒" 机制(典型场景:生产者 - 消费者模型)。

关键方法
  • Wait():释放锁并阻塞,直到被唤醒;
  • Signal():唤醒一个等待的协程;
  • Broadcast():唤醒所有等待的协程;
  • L Locker:绑定的互斥锁(必须传入 Mutex/RWMutex)。
实战示例:生产者 - 消费者模型
复制代码
package main

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

var (
	queue   []int          // 消息队列
	cond    *sync.Cond     // 条件变量
	maxSize = 5            // 队列最大容量
)

// 生产者
func producer(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 0; i < 3; i++ {
		cond.L.Lock() // 加锁
		// 队列满则等待
		for len(queue) >= maxSize {
			cond.Wait() // 释放锁并阻塞
		}
		// 生产数据
		data := id*10 + i
		queue = append(queue, data)
		fmt.Printf("生产者%d生产:%d,队列长度:%d\n", id, data, len(queue))
		cond.L.Unlock() // 解锁
		cond.Signal()   // 唤醒一个消费者
		time.Sleep(100 * time.Millisecond)
	}
}

// 消费者
func consumer(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		cond.L.Lock()
		// 队列空则等待
		for len(queue) == 0 {
			cond.Wait()
		}
		// 消费数据
		data := queue[0]
		queue = queue[1:]
		fmt.Printf("消费者%d消费:%d,队列长度:%d\n", id, data, len(queue))
		cond.L.Unlock()
		cond.Signal() // 唤醒一个生产者
		time.Sleep(200 * time.Millisecond)
	}
}

func main() {
	// 初始化条件变量(绑定互斥锁)
	cond = sync.NewCond(&sync.Mutex{})
	var wg sync.WaitGroup

	// 启动2个生产者、3个消费者
	wg.Add(2)
	go producer(1, &wg)
	go producer(2, &wg)

	wg.Add(3)
	go consumer(1, &wg)
	go consumer(2, &wg)
	go consumer(3, &wg)

	wg.Wait()
}
避坑要点
  • Wait()必须在循环中调用:防止虚假唤醒(协程被唤醒后,条件可能仍不满足);
  • 调用Wait()前必须先加锁:否则会 panic。

8. sync/atomic:原子操作(附属子包)

核心作用

无锁实现简单数值的原子性操作(如计数器、标志位),性能优于 Mutex。

常用方法
  • atomic.AddInt64(&v, 1):原子自增;
  • atomic.StoreInt64(&v, 10):原子赋值;
  • atomic.LoadInt64(&v):原子读取;
  • atomic.CompareAndSwapInt64(&v, old, new):CAS 操作。
实战示例:原子计数器
复制代码
package main

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

var count int64 // 原子操作必须用int64/uint64等类型

func add(wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 0; i < 1000; i++ {
		atomic.AddInt64(&count, 1) // 原子自增(无锁)
	}
}

func main() {
	var wg sync.WaitGroup
	wg.Add(10)

	for i := 0; i < 10; i++ {
		go add(&wg)
	}

	wg.Wait()
	fmt.Printf("最终计数:%d(预期10000)\n", count)
}
适用场景
  • 简单数值的原子操作(计数器、标志位);
  • 复杂操作(如结构体修改)仍需用 Mutex。

三、sync 库使用原则

  1. 最小锁粒度:仅对临界区加锁,避免锁包裹整个函数;
  2. 优先原子操作:简单数值操作用 atomic,而非 Mutex;
  3. 读写分离:多读少写用 RWMutex,纯写用 Mutex;
  4. 避免死锁
    • 不要在加锁后调用阻塞函数(如 IO、sleep);
    • 避免协程间交叉加锁;
  5. Pool 不存持久数据:Pool 中的对象可能被 GC 回收,仅用于临时对象复用。

四、总结

sync库是 Go 进程内并发编程的核心,不同组件适配不同场景:

  • 共享资源保护:Mutex(通用)、RWMutex(多读少写)、atomic(简单数值);
  • 协程同步:WaitGroup(批量等待)、Cond(条件唤醒);
  • 资源复用:sync.Map(并发 Map)、Pool(临时对象);
  • 单次执行:Once(单例初始化)。
相关推荐
JaguarJack2 小时前
PHP 初学者指南 基础结构与常见错误
后端·php
草莓熊Lotso2 小时前
C++ 异常完全指南:从语法到实战,优雅处理程序错误
android·java·开发语言·c++·人工智能·经验分享·后端
init_23612 小时前
MPLS跨域optionA 配置案例
java·开发语言·网络
IT_陈寒2 小时前
Python性能优化实战:7个让代码提速300%的冷门技巧(附基准测试)
前端·人工智能·后端
墨有6662 小时前
【C++ 入门】类和对象(中)
开发语言·c++
雪夜行人2 小时前
cobra命令行工具
开发语言·golang
王家视频教程图书馆2 小时前
C#使用 tcp socket控制台程序和unity客户端通信
开发语言·tcp/ip·c#
小兜全糖(xdqt)2 小时前
python cobnfluent kafka transaction事务
开发语言·python·kafka
新鲜势力呀2 小时前
低成本实现轻量级 Claude 风格对话交互 ——PHP 极简版开发详解
开发语言·php·交互