当前位置:首页 > 技术知识 > 正文内容

一文弄懂 GO 的 互斥锁 Mutex !(互斥锁的使用方法)

maynowei3周前 (08-03)技术知识14

在 Go 语言并发编程中,互斥锁(Mutex)是一个非常重要的同步原语。本文将深入介绍 Mutex 的使用方法、实现原理以及最佳实践。

1. 什么是 Mutex?

Mutex(互斥锁)是一种用于多线程编程中防止竞态条件的同步机制。它能够保证在同一时刻只有一个 goroutine 可以访问共享资源,从而避免数据竞争问题。

Go 语言中的 Mutex 定义在 sync 包中:

type Mutex struct {

}
 #技术分享 #掘金

2. Mutex 的基本用法

2.1 简单示例

package main

import ( "fmt" "sync" )

type Counter struct { mu sync.Mutex count int }

func (c *Counter) Increment() { c.mu.Lock() defer c.mu.Unlock() c.count++ }

func main() { counter := Counter{} var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() counter.Increment() }() } wg.Wait() fmt.Println("Final count:", counter.count) }

2.2 主要方法

Mutex 提供了两个主要方法:

  • Lock() :获取锁
  • Unlock() :释放锁

3. Mutex 的注意事项

3.1 避免死锁

func (c *Counter) BadPractice() {
    c.mu.Lock()
    c.mu.Lock()
    c.count++

}

func (c *Counter) GoodPractice() { c.mu.Lock() defer c.mu.Unlock() c.count++ }

3.2 锁的粒度

func (c *Counter) CoarseLock() {
    c.mu.Lock()
    defer c.mu.Unlock()

    time.Sleep(time.Second)
    c.count++
}

func (c *Counter) FineLock() { time.Sleep(time.Second) c.mu.Lock() defer c.mu.Unlock() c.count++ }

4. Mutex 的高级特性

4.1 模式切换

sync.Mutex 根据情况在 正常模式饥饿模式 之间进行切换,在并发性能与公平性之间实现动态平衡。

正常模式(Non-Fair Mode,默认模式)

在此模式下,允许新请求的 Goroutine 抢占锁,实现最大化吞吐量。

锁竞争流程

新请求锁的 Goroutine 先尝试通过 CAS 直接抢占锁,若抢占失败,则判断当前是否满足自旋条件(下文会讲到),满足则自旋重试,否则则加入 FIFO 等待队列并阻塞。

当锁释放时,即唤醒队列头部的 GOroutine,同时也允许新请求的 GOroutine 抢占,但是由于新请求已在 CPU 上了,所以往往比刚唤醒的 Goroutine 更容易抢到锁,导致被唤醒的 Goroutine 又继续被阻塞。

性能特点

高吞吐量:新请求 Goroutine 可以插队,可以减少上下文切换;潜在不公平:等待队列中的 Goroutine 有可能一直抢占不到锁,出现饥饿的情况。

饥饿模式(Starvation Mode)

该模式主要是为了解决长时间等待的 Goroutine “饿死” 问题,保证公平性。触发条件

  • 任一 Goroutine 等待时间 >= 1ms
  • 等待队列非空且仅剩一个 Goroutine 时(Go 1.9+ 优化)

当队列仅剩一个 Goroutine 时,表明已处于低竞争状态,此时强制饥饿模式可 加速队列清空 。而竞争只会增加额外的上下文切换和调度开销,而不会提升吞吐量,可能会出现 模式振荡 ,导致频繁的模式切换(如新请求突然涌入又触发饥饿模式),而直接进入饥饿模式可稳定完成最后的锁移交。

锁竞争流程

解锁时直接 将锁交给等待队列头部的 Goroutine ,新请求的 Goroutine 无法参与竞争,而是插入队列尾部。

退出条件

当队列头部的 Goroutine 等待时间 <1ms队列为空 时,切换回正常模式

性能特点

  • 高公平性:先到先得原则
  • 低吞吐:禁止新请求插队,增加上下文切换开销

4.2 自旋机制

即在 Goroutine 获取锁失败时,在满足自旋条件的情况下,允许该 Goroutine 重试上锁,避免短期锁等待导致 Goroutine 阻塞,尽量减少上下文切换开销。

自旋条件

  1. 锁已被占用,并且锁不处于饥饿模式(饥饿模式下禁止自旋)。
  2. 积累的自旋次数小于最大自旋次数(active_spin=4)。
  3. cpu 核数大于 1,单核自旋无意义。
  4. 有空闲的 P。
  5. 调度器空闲 :当前 P(Processor)的本地运行队列为空,且无其他自旋中的 M(Machine Thread)。

4.3 读写锁 (RWMutex)

当读操作远多于写操作时,使用 RWMutex 可以提高并发性能:

type DataStore struct {
    rwmu sync.RWMutex
    data map[string]string
}

func (ds *DataStore) Read(key string) string { ds.rwmu.RLock() defer ds.rwmu.RUnlock() return ds.data[key] }

func (ds *DataStore) Write(key, value string) { ds.rwmu.Lock() defer ds.rwmu.Unlock() ds.data[key] = value }

5. 性能优化建议

  1. 避免锁复制
func copyMutex(m sync.Mutex) { ... }

func copyMutex(m *sync.Mutex) { ... }
  1. 合理使用 defer
func quickLock(c *Counter) {
    c.mu.Lock()
    c.count++

}
  1. 减少锁竞争
type ShardedMap struct {
    shards    [256]struct {
        sync.Mutex
        data map[string]string
    }
}

func (m *ShardedMap) getShardIndex(key string) int { return int(hash(key) % 256) }

6. 常见错误模式

6.1 重复解锁

func (c *Counter) Wrong() {
    c.mu.Lock()
    c.count++
    c.mu.Unlock()
    c.mu.Unlock()
}

6.2 忘记解锁

func (c *Counter) Wrong() {
    c.mu.Lock()
    if c.count < 0 {
        return
    }
    c.count++

}

func (c *Counter) Correct() { c.mu.Lock() defer c.mu.Unlock() if c.count < 0 { return } c.count++ }

总结

  1. Mutex 是 Go 语言中重要的同步原语,用于保护共享资源。
  2. 正确使用 Mutex 需要注意避免死锁、合理控制锁粒度。
  3. RWMutex 适用于读多写少的场景。
  4. 性能优化时要注意避免锁复制、减少锁竞争。
  5. 使用 defer 确保锁的正确释放。

掌握 Mutex 的正确使用方式对于编写高质量的并发程序至关重要。在实际开发中,要根据具体场景选择合适的同步策略,既要确保程序的正确性,也要兼顾性能。

优质项目推荐

推荐一个可用于练手、毕业设计参考、增加简历亮点的项目。


lemon-puls/txing-oj-backend: Txing 在线编程学习平台,集在线做题、编程竞赛、即时通讯、文章创作、视频教程、技术论坛为一体

相关文章

单打独斗的产品设计师工作流程总结

来人人都是产品经理【起点学院】,BAT实战派产品总监手把手系统带你学产品、学运营。我从入行开始就在一个做自己产品的小公司工作,到现在已经三年了。刚开始工作的时候什么也不懂,老板说让出效果图,就开始直接...

从零搭建网站?5个步骤详解网站建设全流程

在数字化时代,拥有一个专业网站已成为企业或个人品牌展示的核心竞争力。但对于新手而言,网站建设往往显得复杂难懂。本文梳理出5大关键步骤,手把手带你完成从策划到上线的全流程,省时省力不踩坑!一、前期准备:...

大势所趋:Swift受欢迎度即将赶超Objective C

Swift是Apple在WWDC2014所发布的一门编程语言,用来撰写OS X和iOS应用程序。不到两年时间,在iOS开发者中Swift语言便凭借着简洁的语法和优秀的特性打动了开发者,之前用于iOS和...

单片机C语言编程,心得都在这里了

单片机写代码总踩坑,头文件被无视,老工程师的经验哪里来?前几天写8x8矩阵键盘的程序,搞了三天代码一直乱报错。后来发现自己连头文件是什么都不清楚,之前写的都是小程序,压根没碰过.h文件。看别人的程序都...

msf系列篇章之七模块详解,黑客必学

1、 mestasploit有很多模块,一共分为七类那如果是kali中自带的msf,它默认的安装路径是在这里。,然后可以看见它这些模块有些相对应的目录。1)、exploits漏洞利用模块,这个模块通常...

网络安全常用术语(网络安全常用术语介绍)

黑客帽子之分白帽白帽:亦称白帽黑客、白帽子黑客,是指那些专门研究或者从事网络、计算机技术防御的人,他们通常受雇于各大公司,是维护世界网络、计算机安全的主要力量。很多白帽还受雇于公司,对产品进行模拟黑客...