首页 > 后端开发 > Golang > 正文

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

P粉602998670
发布: 2025-09-03 09:56:01
原创
1023人浏览过
答案: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++
登录后复制
看起来是一个简单的操作,但实际上它包含至少三个步骤:

FaceSwapper
FaceSwapper

FaceSwapper是一款AI在线换脸工具,可以让用户在照片和视频中无缝交换面孔。

FaceSwapper 729
查看详情 FaceSwapper
  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 mutex互斥锁使用方法与示例的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

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