深入解析 Golang 并发编程中的同步机制:WaitGroup 与 Mutex 详解

文章目录

      • 一、简介
      • [二、WaitGroup 的使用](#二、WaitGroup 的使用)
        • [1. 什么是 WaitGroup?](#1. 什么是 WaitGroup?)
        • [2. 基本操作](#2. 基本操作)
        • [3. WaitGroup 示例](#3. WaitGroup 示例)
        • [4. 注意事项](#4. 注意事项)
      • [三、Mutex 的使用](#三、Mutex 的使用)
        • [1. 什么是 Mutex?](#1. 什么是 Mutex?)
        • [2. 基本操作](#2. 基本操作)
        • [3. Mutex 示例](#3. Mutex 示例)
      • 四、竞争条件示例与解决
        • [1. 竞争条件问题示例](#1. 竞争条件问题示例)
        • [2. 使用 Mutex 解决竞争条件](#2. 使用 Mutex 解决竞争条件)
      • [五、使用 RWMutex 实现读写锁](#五、使用 RWMutex 实现读写锁)
        • [RWMutex 示例](#RWMutex 示例)
      • 六、小结

一、简介

在 Golang 的并发编程中,sync 包提供了一些基本的同步原语,用于解决多个 Goroutine 之间的协调与资源共享问题。常用的工具包括:

  • WaitGroup:用于等待一组 Goroutine 完成任务。
  • Mutex:用于保护共享资源,避免多个 Goroutine 同时修改数据导致竞争条件(Race Condition)。

本篇博客将详细介绍 WaitGroupMutex 的用法,并通过示例帮助初学者理解它们在实际场景中的应用。


二、WaitGroup 的使用

1. 什么是 WaitGroup?

WaitGroup 是一个计数器,用于等待多个 Goroutine 完成任务。它可以让主 Goroutine 阻塞,直到所有子 Goroutine 完成工作。

2. 基本操作
  • Add(delta int):添加 delta 个 Goroutine 到计数器。
  • Done():每个 Goroutine 在完成任务后调用,减少计数器的值。
  • Wait():阻塞调用者,直到计数器变为 0。
3. WaitGroup 示例
go 复制代码
package main

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

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 任务完成时调用 Done
    fmt.Printf("Worker %d starting...\n", id)
    time.Sleep(time.Second) // 模拟任务执行
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup // 创建 WaitGroup 实例

    for i := 1; i <= 3; i++ {
        wg.Add(1) // 每启动一个 Goroutine,计数器加 1
        go worker(i, &wg)
    }

    wg.Wait() // 等待所有 Goroutine 完成
    fmt.Println("All workers done!")
}

输出:

复制代码
Worker 1 starting...
Worker 2 starting...
Worker 3 starting...
Worker 1 done
Worker 2 done
Worker 3 done
All workers done!
4. 注意事项
  • 确保 Done 与 Add 匹配 :每次 Add 对应一次 Done,否则可能会导致 Wait 永远阻塞。
  • 并发调用 Add :如果需要在运行时动态启动 Goroutine,应确保 Add 操作发生在 Wait 之前。

三、Mutex 的使用

1. 什么是 Mutex?

Mutex (互斥锁)用于防止多个 Goroutine 同时访问共享资源,从而避免竞争条件。只有一个 Goroutine 能够在同一时间持有 Mutex,其它 Goroutine 必须等待。

2. 基本操作
  • Lock():获取锁。如果锁已被其他 Goroutine 持有,则阻塞。
  • Unlock():释放锁。
3. Mutex 示例
go 复制代码
package main

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

var (
    counter int          // 共享变量
    mutex   sync.Mutex   // 互斥锁保护共享变量
)

func increment(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < 5; i++ {
        mutex.Lock()   // 获取锁
        counter++      // 修改共享变量
        fmt.Printf("Goroutine %d incremented counter to %d\n", id, counter)
        mutex.Unlock() // 释放锁
        time.Sleep(time.Millisecond * 100) // 模拟其他操作
    }
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go increment(i, &wg)
    }

    wg.Wait()
    fmt.Println("Final Counter Value:", counter)
}

输出(示例):

复制代码
Goroutine 1 incremented counter to 1
Goroutine 2 incremented counter to 2
Goroutine 3 incremented counter to 3
...
Final Counter Value: 15

四、竞争条件示例与解决

1. 竞争条件问题示例

如果不使用 Mutex,多个 Goroutine 同时修改共享变量时会产生不一致的数据

go 复制代码
package main

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

var counter int // 共享变量

func increment(wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < 5; i++ {
        counter++
        time.Sleep(time.Millisecond * 100) // 模拟其他操作
    }
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go increment(&wg)
    }

    wg.Wait()
    fmt.Println("Final Counter Value:", counter)
}

输出:

复制代码
Final Counter Value: 12

由于多个 Goroutine 竞争访问 counter,导致最终值不正确。


2. 使用 Mutex 解决竞争条件
go 复制代码
mutex.Lock()
counter++
mutex.Unlock()

通过为每次修改操作加锁,确保同一时间只有一个 Goroutine 可以修改 counter,从而避免数据不一致。


五、使用 RWMutex 实现读写锁

RWMutex 是一种读写锁,允许多个 Goroutine 同时读取,但在写入时需要独占锁。

RWMutex 示例
go 复制代码
package main

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

var (
    data  = 0
    rwMux sync.RWMutex
)

func readData(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    rwMux.RLock() // 获取读锁
    fmt.Printf("Goroutine %d reading data: %d\n", id, data)
    rwMux.RUnlock() // 释放读锁
}

func writeData(wg *sync.WaitGroup) {
    defer wg.Done()
    rwMux.Lock() // 获取写锁
    data++
    fmt.Printf("Writing data: %d\n", data)
    rwMux.Unlock() // 释放写锁
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go readData(i, &wg)
    }

    wg.Add(1)
    go writeData(&wg)

    wg.Wait()
}

六、小结

  1. WaitGroup 用于等待一组 Goroutine 完成任务,是 Goroutine 同步的常用工具。
  2. MutexRWMutex 用于保护共享资源,避免竞争条件。
  3. 使用锁时需注意避免死锁性能瓶颈

在下一篇博客中,我们将介绍 Golang 的 Context 包,并探讨如何在并发操作中控制超时和取消任务。敬请期待!

相关推荐
面向Google编程1 分钟前
Apache Iceberg Variant 类型:v3 半结构化数据不再「二选一」
大数据·后端
jason成都39 分钟前
Spring WebFlux 适配达梦新方案|dm‑r2dbc:Netty 异步传输的实验性 R2DBC 驱动
java·后端·spring
泡海椒1 小时前
内置SPI函数库详解:JQuick-Java Builtin工具类实战用法
java·开发语言·python
hqyjzsb1 小时前
零 AI 项目经验,学 Python 转型 AI 的正确顺序是什么?
开发语言·人工智能·python·算法·职场和发展·数据挖掘·数据分析
Keven_112 小时前
算法札记:ACM适用的很骚的C++语法(持续更新)
开发语言·c++
北岛贰2 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
脉动数据行情12 小时前
C# 实现德指 DAX 期货 WebSocket 实时行情监听
开发语言·websocket·c#
大圣编蚕3 小时前
Java ByteArrayInputStream 详解:从入门到实战
java·开发语言·算法
菜鸟~noob2333 小时前
【电子战】第02篇:能量检测器 vs. 相关检测器【含matlab代码】
开发语言·matlab
OpenAnolis小助手4 小时前
一句话看透 JVM,SysOM 诊断 Skill 新增 Java 应用诊断能力
java·开发语言·jvm·阿里云·操作系统控制台·sysom