Golang 并发机制-4:用Mutex管理共享资源

news/2025/2/4 18:32:14 标签: golang, 开发语言

并发性是Go的强大功能之一,它允许多个线程(并发线程)同时执行。然而,权力越大,责任越大。当多个例程并发地访问和修改共享资源时,可能会导致数据损坏、竞争条件和不可预测的程序行为。为了解决这些问题,Go提供了一个称为互斥的同步原语(互斥的缩写)。在本文中,我们将探讨Mutex在管理共享资源中的作用,以及在并发编程中使用它的必要性。

互斥锁简介

互斥锁是一种同步原语,提供对共享资源或代码关键段的独占访问。它充当看门人,一次只允许一个Goroutine访问和修改受保护的资源。当一个线程持有互斥锁时,所有其他试图获取互斥锁的线程都必须等待轮到它们。
在这里插入图片描述

互斥锁提供了两个基本方法:

  • Lock() :该方法获取互斥对象,授予对资源的独占访问权。如果另一个线程已经持有互斥对象,新的线程将阻塞,直到它被释放。
  • Unlock():这个方法释放互斥锁,允许其他等待的例程获取互斥锁并访问资源。

互斥锁应用场景

对互斥锁的需求源于这样一个事实,即共享资源在被多个例程并发访问时容易受到数据竞争和不一致的影响。以下是互斥锁必不可少的一些常见场景:

  • 数据竞争

当多个协程并发地访问共享数据,并且其中至少有一个修改共享数据时,就会发生数据竞争。这可能导致不可预测的错误行为,因为无法保证执行顺序。互斥锁通过一次只允许一个协程访问共享资源来防止数据竞争。

package main

import (
    "fmt"
    "sync"
)

var sharedData int
var mu sync.Mutex

func increment() {
    mu.Lock()
    sharedData++
    mu.Unlock()
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            increment()
        }()
    }
    wg.Wait()
    fmt.Println("Shared Data:", sharedData)
}

在这个例子中,多个协程并发地增加sharedData变量,这将导致没有互斥锁的数据竞争。

  • 临界区

临界区是访问共享资源的代码的一部分。当多个go例程试图同时访问同一个临界区时,可能会导致不可预测的行为。互斥锁确保一次只有一个程序进入临界区,保证对共享资源的有序访问。

package main

import (
    "fmt"
    "sync"
)

var (
    sharedResource int
    mu             sync.Mutex
)

func updateSharedResource() {
    mu.Lock()
    // Critical section: Access and modify sharedResource
    sharedResource++
    mu.Unlock()
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            updateSharedResource()
        }()
    }
    wg.Wait()
    fmt.Println("Shared Resource:", sharedResource)
}

在这个例子中,updateSharedResource 函数代表了一个关键区域,其中sharedResource被访问和修改。如果没有互斥锁,对这个临界区的并发访问可能会导致不正确的结果。

互斥锁方法

互斥锁提供了两种基本操作:锁定和解锁。让我们从理解互斥锁开始:

  • 锁定互斥对象:当一个线程想要访问共享资源或临界区时,它会调用互斥对象上的Lock()方法。如果互斥对象当前处于未锁定状态,它将被锁定,从而允许线程程继续执行。如果互斥锁已经被另一个线程锁住了,调用的线程将被阻塞,直到互斥锁可用。

下面是演示互斥锁的代码示例:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var mu sync.Mutex

    mu.Lock() // Lock the Mutex
    // Critical section: Access and modify shared resource
    fmt.Println("Locked the Mutex")
    mu.Unlock() // Unlock the Mutex
}
  • 解锁互斥锁:当一个线程完成了它的临界区并且不再需要独占访问共享资源时,它调用互斥锁上的Unlock()方法。这个动作释放互斥锁,允许其他例程获取它。

下面是互斥锁解锁的执行方式:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var mu sync.Mutex

    mu.Lock() // Lock the Mutex
    // Critical section: Access and modify shared resource
    fmt.Println("Locked the Mutex")
    mu.Unlock() // Unlock the Mutex
    fmt.Println("Unlocked the Mutex")
}

在这个例子中,在临界区之后调用mu.Unlock()来释放互斥锁,使其可供其他例程使用。

避免死锁

虽然互斥锁是确保并发安全性的强大工具,但如果使用不当,它们也会引入死锁。当两个或多个线程被卡住,等待对方释放资源时,就会发生死锁。要避免死锁,请遵循以下最佳实践:

  1. Always Unlock:确保互斥锁在锁定后处于解锁状态。如果不这样做,可能会导致死锁。
  2. 使用defer:为了保证互斥锁总是被解锁,可以考虑在函数结束时使用defer语句来解锁它们。
  3. 避免循环依赖:要小心循环依赖,在循环依赖中,多个例程会等待彼此释放资源。设计代码以避免这种情况。
package main

import (
    "fmt"
    "sync"
)

func main() {
    var mu sync.Mutex

    mu.Lock() // Lock the Mutex
    // Critical section: Access and modify shared resource

    // Oops! Forgot to unlock the Mutex
    // mu.Unlock() // Uncomment this line to avoid deadlock
    fmt.Println("Locked the Mutex")

    // ... Some more code

    // Potential deadlock if mu.Unlock() is not called
}

在这个例子中,如果‘ mu.Unlock() ’行被遗忘或注释掉,则可能会发生死锁,因为互斥对象无限期地处于锁定状态。

互斥锁vs通道

互斥锁并不是Go中管理并发性的唯一工具;通道是另一种基本机制。下面是互斥锁和通道的简要比较:

  • 互斥锁用于保护临界区,并确保对共享资源的独占访问。它们适用于需要对数据访问进行细粒度控制的情况。
  • 通道用于程序间的通信和同步。它们为交换数据和同步程序提供了更高层次的抽象。

互斥锁和通道之间的选择取决于程序的具体要求。互斥锁对于需要保护共享数据的场景是理想的,而通道则适合于主要关注程序之间的通信和协调的场景。

最后总结

总之,互斥锁是确保Go中安全并发的强大工具。它们有助于保护关键区,防止数据争用,并确保共享资源的完整性。了解何时以及如何使用互斥锁对于编写既高效又可靠的并发Go程序至关重要。


http://www.niftyadmin.cn/n/5841740.html

相关文章

【PyTorch】7.自动微分模块:开启神经网络 “进化之门” 的魔法钥匙

目录 1. 梯度基本计算 2. 控制梯度计算 3. 梯度计算注意 4. 小节 个人主页&#xff1a;Icomi 专栏地址&#xff1a;PyTorch入门 在深度学习蓬勃发展的当下&#xff0c;PyTorch 是不可或缺的工具。它作为强大的深度学习框架&#xff0c;为构建和训练神经网络提供了高效且灵活…

centos如何压缩zip

在CentOS系统中&#xff0c;压缩和解压缩文件是常见的任务之一。zip命令行工具可以方便地将文件或目录压缩成zip格式文件。本文将详细介绍如何在CentOS上安装并使用zip工具进行文件和目录的压缩。 安装zip工具 首先&#xff0c;确保系统安装了zip工具。如果未安装&#xff0c…

数据结构:时间复杂度

文章目录 为什么需要时间复杂度分析&#xff1f;一、大O表示法&#xff1a;复杂度的语言1.1 什么是大O&#xff1f;1.2 常见复杂度速查表 二、实战分析&#xff1a;解剖C语言代码2.1 循环结构的三重境界单层循环&#xff1a;线性时间双重循环&#xff1a;平方时间动态边界循环&…

【玩转 Postman 接口测试与开发2_015】第12章:模拟服务器(Mock servers)在 Postman 中的创建与用法(含完整实测效果图)

《API Testing and Development with Postman》最新第二版封面 文章目录 第十二章 模拟服务器&#xff08;Mock servers&#xff09;在 Postman 中的创建与用法1 模拟服务器的概念2 模拟服务器的创建2.1 开启侧边栏2.2 模拟服务器的两种创建方式2.3 私有模拟器的 API 秘钥的用法…

Leetcode—1427. 字符串的左右移【简单】Plus

2025每日刷题&#xff08;206&#xff09; Leetcode—1427. 字符串的左右移 实现代码 class Solution { public:string stringShift(string s, vector<vector<int>>& shift) {// shift[i] [dir, amount]// dir 0(左) or 1(右)// 左表示正, 右表示负int len…

音标-- 02-- 重音 音节 变音

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 国际音标1.重音2.音节3.变音 国际音标 1.重音 2.音节 3.变音

锦州市建筑房屋轮廓面图层数据+shp格式arcgis无偏移

锦州市建筑房屋轮廓面图层数据shp格式arcgis无偏移

VirtualBox:跨磁盘导入已存的vdi磁盘文件顺便测试冷迁移

目录 1.背景 2.目的 3.步骤 3.1 安装在移动硬盘上 3.2.接管现有主机磁盘上的虚拟机 3.3接管迁移到移动硬盘的虚拟机 4. 结论 1.背景 电脑重新做了系统&#xff0c;然后找不到virtualbox的启动程序了&#xff0c;另外电脑磁盘由于存储了其他文件已经爆红&#xff0c;无法…