0

0

Golang mutex互斥锁使用方法与示例

P粉602998670

P粉602998670

发布时间:2025-09-03 09:56:01

|

1031人浏览过

|

来源于php中文网

原创

答案:sync.Mutex通过Lock和Unlock控制并发访问,防止竞态条件,确保共享资源安全,使用defer可避免忘记释放锁导致的死锁问题。

golang mutex互斥锁使用方法与示例

在Golang里,

sync.Mutex
互斥锁是处理并发访问共享资源的核心工具,它能确保同一时间只有一个goroutine能修改某块数据,从而避免竞态条件(race condition)导致的数据混乱。我个人觉得,理解并正确使用它,是写出健壮并发程序的基石,虽然概念简单,但实际应用中总有些小坑需要注意。

sync.Mutex
提供了两个基本方法:
Lock()
Unlock()
。当一个goroutine调用
Lock()
时,它会尝试获取锁;如果锁已经被其他goroutine持有,它就会阻塞,直到锁被释放。一旦获取到锁,它就可以安全地访问共享资源。完成操作后,必须调用
Unlock()
来释放锁,让其他等待的goroutine有机会获取。

一个经典的例子就是对一个计数器进行并发递增操作。没有锁的情况下,你会发现最终结果往往不符合预期:

package main

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

var counter int

func incrementWithoutLock() {
    for i := 0; i < 1000; i++ {
        counter++
    }
}

func main() {
    // 演示没有锁的情况
    counter = 0
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            incrementWithoutLock()
        }()
    }
    wg.Wait()
    fmt.Printf("没有锁的最终计数: %d (预期: 10000)\n", counter) // 结果通常小于10000

    // 使用互斥锁解决竞态条件
    counter = 0 // 重置计数器
    var mu sync.Mutex // 声明一个互斥锁
    var wgWithLock sync.WaitGroup

    for i := 0; i < 10; i++ {
        wgWithLock.Add(1)
        go func() {
            defer wgWithLock.Done()
            for j := 0; j < 1000; j++ {
                mu.Lock()   // 获取锁
                counter++
                mu.Unlock() // 释放锁
            }
        }()
    }
    wgWithLock.Wait()
    fmt.Printf("使用互斥锁的最终计数: %d (预期: 10000)\n", counter) // 结果总是10000

    // 更优雅的写法,使用defer确保解锁
    counter = 0
    var mu2 sync.Mutex
    var wgWithDefer sync.WaitGroup

    for i := 0; i < 10; i++ {
        wgWithDefer.Add(1)
        go func() {
            defer wgWithDefer.Done()
            for j := 0; j < 1000; j++ {
                mu2.Lock()
                defer mu2.Unlock() // 使用defer确保在函数返回前解锁,即使发生panic
                counter++
            }
        }()
    }
    wgWithDefer.Wait()
    fmt.Printf("使用defer互斥锁的最终计数: %d (预期: 10000)\n", counter)

    // 演示一个稍微复杂点的场景:共享map
    var sharedMap = make(map[string]int)
    var mapMu sync.Mutex
    var mapWg sync.WaitGroup

    for i := 0; i < 5; i++ {
        mapWg.Add(1)
        go func(id int) {
            defer mapWg.Done()
            key := fmt.Sprintf("key_%d", id)
            mapMu.Lock()
            defer mapMu.Unlock()
            sharedMap[key] = id * 10
            time.Sleep(10 * time.Millisecond) // 模拟一些工作
            fmt.Printf("Goroutine %d 写入 %s: %d\n", id, key, sharedMap[key])
        }(i)
    }

    for i := 0; i < 5; i++ {
        mapWg.Add(1)
        go func(id int) {
            defer mapWg.Done()
            key := fmt.Sprintf("key_%d", id)
            mapMu.Lock()
            defer mapMu.Unlock()
            if val, ok := sharedMap[key]; ok {
                fmt.Printf("Goroutine %d 读取 %s: %d\n", id, key, val)
            } else {
                fmt.Printf("Goroutine %d 尝试读取 %s,但未找到\n", id, key)
            }
            time.Sleep(5 * time.Millisecond) // 模拟一些工作
        }(i)
    }
    mapWg.Wait()
    fmt.Println("共享Map最终内容:", sharedMap)
}

并发编程中为何需要互斥锁?理解竞态条件与数据不一致性

说实话,很多人一开始接触并发编程,都会对“竞态条件”这个词感到有些抽象。简单来说,当多个goroutine(或者说线程)试图同时访问并修改同一个共享资源,而且这些操作的最终结果取决于它们执行的相对时序时,竞态条件就发生了。这就像多个人同时去抢一个座位,谁先坐下,谁就占了,但如果大家同时动,就可能出现混乱。

立即学习go语言免费学习笔记(深入)”;

最常见的例子就是我上面提到的计数器。

counter++
看起来是一个简单的操作,但实际上它包含至少三个步骤:

OpenArt
OpenArt

在线AI绘画艺术图片生成器工具

下载
  1. 读取
    counter
    的当前值。
  2. 将读取到的值加1。
  3. 将新值写回
    counter

设想一下,如果goroutine A读取到

counter
是5,正准备加1;与此同时,goroutine B也读取到
counter
是5,也准备加1。如果A先完成了写回操作,
counter
变成了6。但紧接着B也完成了写回操作(它基于旧值5加1),
counter
又变成了6。这样,两次递增操作,最终只让计数器增加了1,而不是预期的2。这就是典型的数据不一致性,而且这种错误很难复现,因为它依赖于不确定的调度顺序,让人抓狂。

互斥锁的作用,就是强制这些对共享资源的操作“串行化”。当一个goroutine获取了锁,它就拥有了对这块资源的“独占权”,其他想访问这块资源的goroutine就得排队等待。这样,上面的

counter++
操作就变成了一个原子操作,要么全部完成,要么不开始,从而彻底消除了竞态条件,保证了数据在并发环境下的正确性。没有互斥锁,你的并发程序就像一辆没有红绿灯的交叉路口,迟早会发生事故。

Golang互斥锁有哪些常见的陷阱与最佳实践?

互斥锁用起来简单,但要用好,避免“踩坑”,还是有些地方需要注意的。我个人就遇到过几次因为锁使用不当导致的死锁或者性能问题,那调试起来真是让人头大。

  1. 忘记解锁 (Forgetting to Unlock):这是最常见的错误之一。如果

    Lock()
    了却没
    Unlock()
    ,那么其他所有尝试获取这个锁的goroutine都会永久阻塞,导致程序死锁。Go社区的惯例是使用
    defer mu.Unlock()
    。这样可以确保无论函数如何退出(正常返回、panic),锁都会被释放,大大降低了出错的概率。上面的示例里也展示了这种写法。

  2. 死锁 (Deadlock):当两个或多个goroutine互相等待对方释放资源时,就会发生死锁。一个经典的例子是“哲学家就餐问题”。在实际代码中,这通常发生在尝试获取多个锁时,如果获取锁的顺序不一致,就很容易形成循环等待。

    • 避免策略:始终以相同的顺序获取多个锁。如果可能,尽量只锁住一个

相关专题

更多
golang如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

178

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

226

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

339

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

209

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

391

2024.05.21

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

196

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

191

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

192

2025.06.17

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

43

2026.01.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 3.9万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号