惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
V
Vulnerabilities – Threatpost
C
CERT Recently Published Vulnerability Notes
Google DeepMind News
Google DeepMind News
GbyAI
GbyAI
Y
Y Combinator Blog
T
Threatpost
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Project Zero
Project Zero
Engineering at Meta
Engineering at Meta
MongoDB | Blog
MongoDB | Blog
MyScale Blog
MyScale Blog
Security Latest
Security Latest
T
Threat Research - Cisco Blogs
量子位
I
Intezer
Simon Willison's Weblog
Simon Willison's Weblog
C
Cybersecurity and Infrastructure Security Agency CISA
L
Lohrmann on Cybersecurity
L
LINUX DO - 最新话题
The Register - Security
The Register - Security
T
Tailwind CSS Blog
爱范儿
爱范儿
Google DeepMind News
Google DeepMind News
T
Troy Hunt's Blog
Stack Overflow Blog
Stack Overflow Blog
Cloudbric
Cloudbric
S
Secure Thoughts
The GitHub Blog
The GitHub Blog
T
The Blog of Author Tim Ferriss
L
LangChain Blog
Recorded Future
Recorded Future
小众软件
小众软件
www.infosecurity-magazine.com
www.infosecurity-magazine.com
T
Tor Project blog
人人都是产品经理
人人都是产品经理
F
Full Disclosure
O
OpenAI News
Webroot Blog
Webroot Blog
A
Arctic Wolf
TaoSecurity Blog
TaoSecurity Blog
P
Privacy & Cybersecurity Law Blog
Jina AI
Jina AI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
雷峰网
雷峰网
Microsoft Security Blog
Microsoft Security Blog
H
Heimdal Security Blog
B
Blog RSS Feed
Vercel News
Vercel News

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
第09章 - 并发进阶:sync包与并发控制
我才是银古 · 2026-06-19 · via 博客园 - 我才是银古

第09章 - 并发进阶:sync包与并发控制

虽然 Go 推崇用 channel 通信,但在某些场景下,传统的共享内存加锁同步更简洁高效。本章讲解 sync 包提供的同步原语、sync/atomic 原子操作、context 包以及并发安全的注意事项。

9.1 何时用锁,何时用 channel

Go 社区有一条经验法则:

  • 传递数据所有权、协调 goroutine → 使用 channel。
  • 保护共享状态、简单的计数与缓存 → 使用 sync 包的锁。

两者并非对立,应根据场景选择更清晰的方案。"通过通信共享内存"是首选,但不是教条。

9.2 互斥锁 sync.Mutex

sync.Mutex 是最常用的同步原语,用于保护临界区,确保同一时刻只有一个 goroutine 访问共享资源:

type Counter struct {
    mu    sync.Mutex
    count int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock() // 保证解锁,即使 panic
    c.count++
}

func (c *Counter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

9.2.1 使用要点

  • Lock()Unlock() 必须成对出现,通常用 defer 确保解锁。
  • Mutex 是值类型,不能复制(复制后两个锁互不影响)。包含 Mutex 的结构体应通过指针传递。
  • Mutex 不可重入:同一 goroutine 重复 Lock 会死锁。

9.3 读写锁 sync.RWMutex

当读操作远多于写操作时,sync.RWMutex 允许多个读者同时持有读锁,但写锁是排他的,能显著提升并发读性能:

type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) string {
    c.mu.RLock()         // 读锁,可并发
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // 写锁,排他
    defer c.mu.Unlock()
    c.data[key] = value
}

9.4 等待组 sync.WaitGroup

sync.WaitGroup 用于等待一组 goroutine 全部完成,是替代"用 channel 计数"的简洁方案:

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1) // 计数器加 1
        go func(id int) {
            defer wg.Done() // 完成时计数器减 1
            fmt.Printf("worker %d 完成\n", id)
        }(i)
    }

    wg.Wait() // 阻塞直到计数器归零
    fmt.Println("所有 worker 已完成")
}

9.4.1 使用要点

  • Add 应在启动 goroutine 之前调用,避免竞态。
  • Done 通常用 defer 调用,确保即使 panic 也能递减。
  • WaitGroup 不能复制,传递时用指针。

9.5 一次性执行 sync.Once

sync.Once 确保某个操作在程序运行期间仅执行一次,常用于单例初始化、懒加载:

var (
    instance *Database
    once     sync.Once
)

func GetDatabase() *Database {
    once.Do(func() {
        instance = &Database{ /* 初始化连接 */ }
    })
    return instance
}

无论 GetDatabase 被多少个 goroutine 并发调用,初始化逻辑只会执行一次,且其他 goroutine 会等待初始化完成。

9.6 并发安全的 map:sync.Map

内置 map 不是并发安全的。对于读多写少、键集合相对稳定的场景,可使用 sync.Map

var m sync.Map

m.Store("key", 100)              // 写入
value, ok := m.Load("key")       // 读取
m.Delete("key")                  // 删除
m.LoadOrStore("k", 1)            // 不存在则存储

// 遍历
m.Range(func(key, value any) bool {
    fmt.Println(key, value)
    return true // 返回 false 停止遍历
})

注意sync.Map 并非万能。对于大多数场景,"普通 map + RWMutex"性能更好、更直观。仅在特定读多写少或键稳定的场景才考虑 sync.Map

9.7 原子操作 sync/atomic

对于简单的数值操作,原子操作比互斥锁更高效,因为它直接利用 CPU 的原子指令,无需加锁:

import "sync/atomic"

var counter int64

func increment() {
    atomic.AddInt64(&counter, 1)        // 原子加
}

func read() int64 {
    return atomic.LoadInt64(&counter)   // 原子读
}

Go 1.19 起提供了更易用的原子类型封装:

var counter atomic.Int64
counter.Add(1)
counter.Load()
counter.Store(10)
counter.CompareAndSwap(10, 20) // CAS 操作

9.8 context 包

context 是 Go 并发编程中用于传递取消信号、超时、截止时间和请求范围值的标准机制,在网络服务、API 调用链中无处不在。

9.8.1 创建 context

import "context"

// 根 context
ctx := context.Background()  // 通常作为顶层
ctx := context.TODO()        // 不确定用哪个时的占位

// 可取消的 context
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 释放资源

// 带超时
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

// 带截止时间
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute))
defer cancel()

9.8.2 监听取消信号

goroutine 通过 ctx.Done() channel 感知取消:

func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("收到取消信号:", ctx.Err())
            return
        default:
            // 执行工作
            time.Sleep(100 * time.Millisecond)
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    worker(ctx) // 1 秒后自动取消
}

9.8.3 传递请求范围的值

ctx := context.WithValue(context.Background(), "userID", 12345)
userID := ctx.Value("userID")

最佳实践:context 应作为函数的第一个参数显式传递(命名为 ctx),不要存储在结构体中;WithValue 只用于传递请求范围的元数据(如 trace ID),不要用于传递可选参数。

9.9 竞态检测器

数据竞争(data race)是并发编程中最隐蔽的 bug。Go 内置了强大的竞态检测器,在测试和运行时加上 -race 标志即可启用:

go run -race main.go
go test -race ./...
go build -race

竞态检测器会在运行时监控内存访问,一旦发现多个 goroutine 在没有同步的情况下并发访问同一内存(且至少有一个是写操作),就会报告详细的竞态信息。

强烈建议:在 CI 流程中始终启用 -race 运行测试,及早发现并发问题。

9.10 并发设计原则

  1. 共享状态最小化:尽量减少需要同步的共享数据。
  2. 明确所有权:一份数据在同一时刻应只由一个 goroutine 拥有写权限。
  3. 锁的粒度要合适:锁太粗影响并发,太细容易出错。
  4. 避免嵌套锁:多个锁的获取顺序不一致会导致死锁。
  5. 始终用 -race 测试
  6. 每个 goroutine 都要有退出路径:避免泄漏。

9.11 本章小结

本章讲解了 Go 并发的进阶工具:sync.Mutex/RWMutex 保护共享状态,WaitGroup 等待 goroutine 完成,Once 实现单次初始化,sync.Mapatomic 提供并发安全的数据访问,context 统一管理取消与超时。配合 -race 竞态检测器,可以构建出正确、高效的并发程序。channel 与锁各有适用场景,应根据问题选择最清晰的方案。

下一章我们将学习 Go 的包管理与 Go Modules。