使用互斥锁(Mutex)管理共享资源

news2025/1/19 3:08:51

在这里插入图片描述

在Go中确保并发安全性

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

互斥锁简介

互斥锁是一种同步原语,用于提供对共享资源或代码关键部分的独占访问。它充当了门卫的角色,一次只允许一个Goroutine访问和修改受保护的资源。当一个Goroutine持有互斥锁时,所有试图获取它的其他Goroutines都必须等待。

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

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

互斥锁的必要性

使用互斥锁的原因在于,当多个Goroutines并发访问共享资源时,这些资源容易遭受数据竞争和不一致性的风险。以下是互斥锁至关重要的一些常见场景:

1. 数据竞争

数据竞争发生在多个Goroutines并发访问共享数据时,其中至少一个Goroutine对其进行修改。这可能导致不可预测和错误的行为,因为执行顺序是不确定的。互斥锁通过一次只允许一个Goroutine访问共享资源来帮助防止数据竞争。

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)
}

在这个示例中,多个Goroutines并发地增加sharedData变量,如果没有使用互斥锁,这将导致数据竞争。

2. 临界区(Critical Sections)

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

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。如果没有使用互斥锁,对这个临界区的并发访问可能会导致不正确的结果。

互斥锁定

互斥锁提供了两个基本操作:锁定解锁。让我们首先了解互斥锁的锁定操作:

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

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

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
}

在这个示例中,mu.Lock() 调用锁定了互斥锁,确保一次只有一个Goroutine可以进入临界区。当完成临界区后,使用 mu.Unlock() 解锁互斥锁。

互斥锁解锁

  • 解锁互斥锁:当一个Goroutine完成其临界区的执行并且不再需要对共享资源进行独占访问时,它会在互斥锁上调用 Unlock() 方法。这个操作会释放互斥锁,从而允许其他Goroutines获取它。

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

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() 以释放互斥锁,使其可供其他Goroutines使用。

避免死锁

尽管互斥锁是确保并发安全性的强大工具,但如果使用不当,它们也可能引入死锁。死锁 是指两个或多个Goroutines被卡住,彼此等待释放资源的情况。为了避免死锁,请遵循以下最佳实践:

  1. 始终解锁:确保在锁定后解锁互斥锁。如果不这样做,可能会导致死锁。
  2. 使用 defer:为了确保互斥锁始终被解锁,考虑使用 defer 语句在函数结束时解锁它们。
  3. 避免循环依赖:小心循环依赖的情况,其中多个Goroutines互相等待释放资源。设计代码时要避免这种情况。
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() 这一行,由于互斥锁持续保持锁定状态,可能会发生死锁。

1. 临界区

什么是临界区?

在并发编程中,临界区 是指访问共享资源或变量的代码部分。它被称为“临界”是因为在任何给定时刻只应允许一个Goroutine执行它。当多个Goroutines并发访问一个临界区时,可能会导致数据损坏或竞态条件,其中执行的顺序变得不可预测。

使用互斥锁保护临界区

互斥锁用于保护临界区,确保一次只有一个Goroutine可以访问它们。互斥锁提供了两个基本方法:

  • Lock(): 此方法锁定互斥锁,允许当前的Goroutine进入临界区。如果另一个Goroutine已经锁定了互斥锁,调用该方法的Goroutine将被阻塞,直到互斥锁被释放。
  • Unlock(): 此方法解锁互斥锁,允许其他Goroutines获取它并进入临界区。

以下是一个演示使用互斥锁保护临界区的示例:

package main

import (
    "fmt"
    "sync"
)

var sharedResource int
var mu sync.Mutex

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

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 被访问和修改。互斥锁 mu 确保一次只有一个Goroutine可以进入这个临界区。

2. 互斥锁与通道的比较

互斥锁并不是Go中管理并发的唯一工具;通道也是另一个重要的机制。以下是互斥锁和通道的简要比较:

  • 互斥锁 用于保护临界区并确保对共享资源的独占访问。当您需要对数据访问进行细粒度的控制时,它们非常适用。
  • 通道 用于Goroutines之间的通信和同步。它们为交换数据和同步Goroutines提供了更高级别的抽象。

选择使用互斥锁还是通道取决于您程序的具体需求。当您需要保护共享数据时,互斥锁是理想的选择,而当通信和Goroutines之间的协调是主要关注点时,通道则表现出色。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1331107.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络爬虫之Ajax动态数据采集

动态数据采集 规则 有时候我们在用 requests 抓取页面的时候&#xff0c;得到的结果可能和在浏览器中看到的不一样&#xff0c;在浏览器中可以看到正常显示的页面教据&#xff0c;但是使用 requests 得到的结果并没有&#xff0c;这是因为requests 获取的都是原始的 HTML 文档…

【小白攻略】php 小数转为百分比,保留两位小数的函数

php 小数转为百分比 首先&#xff0c;最简单直观的方法是利用PHP内置的number_format函数。该函数可以对一个数字进行格式化&#xff0c;并可以设置小数点后的精度。通过将小数乘以100&#xff0c;再用number_format函数将结果格式化为百分比形式&#xff0c;即可达到将小数转为…

uniapp怎么动态渲染导航栏的title?

直接在接口请求里面写入以下&#xff1a; 自己要什么参数就写什么参数 本人仅供参考&#xff1a; this.name res.data.data[i].name; console.log(名字, res.data.data[i].name); uni.setNavigationBarTitle({title: this.name}) 效果&#xff1a;

图论 | 网络流的基本概念

文章目录 流网路残留网络增广路径割最大流最小割定理最大流Edmonds-Karp 算法算法步骤程序代码时间复杂度 流网路 流网络&#xff1a; G ( V , E ) G (V, E) G(V,E) 有向图&#xff0c;不考虑反向边s&#xff1a;源点t&#xff1a;汇点 c ( u , v ) c(u, v) c(u,v)&#xff…

CSS:浮动

CSS&#xff1a;浮动 浮动效果浮动方式 float浮动特性标准流脱标脱标的影响脱标的影响范围 清除浮动清除浮动原理 clear基于clear的清除浮动方式额外标签法:afert伪元素法双伪元素法 清除浮动原理 BFCBFC定义BFC布局规则创建一个BFC基于BFC的清除浮动方式父级添加overflow法 浮…

物理模拟重力 斜抛运动计算 抛物线计算

物理模拟重力 斜抛运动计算 抛物线计算 一、介绍二、原理三、实现如下PhysicsUtil.cs 工具类Missile.cs 四、资源分享 一、介绍 模拟Unity原始重力系统进行重写&#xff0c;可是实现发射到指定目标位置并能继续当前力进行自身的弹力与摩擦继续运动 二、原理 将Unity原始不受控…

鸿蒙开发4.0-ArkTS与H5的交互

ArkTS侧与H5的交互 首先在开发H5页面&#xff08;输入框和金额选择部分&#xff09;前需要实现JSBridge桥接打通两侧的交互。开发者可以在ArkTS侧定义一个JSBridge类&#xff0c;在类中封装call方法以及initJsBridge方法。 准备异步执行脚本&#xff0c;在脚本中声明一个JSBri…

工具系列:PyCaret介绍_Fugue 集成_Spark、Dask分布式训练

工具系列&#xff1a;PyCaret介绍_Fugue 集成_Spark、Dask分布式训练 Fugue 是一个低代码的统一接口&#xff0c;用于不同的计算框架&#xff0c;如 Spark、Dask。PyCaret 使用 Fugue 来支持分布式计算场景。 目录 1、分布式计算示例&#xff1a; (1)分类 (2)回归 (3)时间…

7.5组合总和②(LC40-M)

算法&#xff1a; 相比于上一题&#xff0c;数组candidates有重复元素&#xff0c;而要求不能有重复的组合&#xff0c;所以相对于39.组合总和 (opens new window)难度提升了不少。 如何去重&#xff1f; 先把candidates排序&#xff0c;让重复的元素都在一起 单层递归时&a…

MATLAB - 读取双摆杆上的 IMU 数据

系列文章目录 前言 本示例展示了如何从安装在双摆杆上的两个 IMU 传感器生成惯性测量单元 (IMU) 读数。双摆使用 Simscape Multibody™ 进行建模。有关使用 Simscape Multibody™ 构建简易摆的分步示例&#xff0c;请参阅简易摆建模&#xff08;Simscape Multibody&#xff09…

深度学习(七):bert理解之输入形式

传统的预训练方法存在一些问题&#xff0c;如单向语言模型的局限性和无法处理双向上下文的限制。为了解决这些问题&#xff0c;一种新的预训练方法随即被提出&#xff0c;即BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;。通过在大规模…

python通过JS逆向采集艺恩电影数据, 并制作可视化

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 环境使用: 版 本&#xff1a; python 3.10 编辑器&#xff1a;pycharm 2022.3.2 nodejs 模块使用: requests -> pip install requests execjs -> pip install…

python实现bp神经网络对csv文件进行数据预测

参考资源&#xff1a; sklearn库 bp神经网络[从原理到代码一篇搞定]&#xff08;2&#xff09;_sklearn 神经网络-CSDN博客 十分钟上手sklearn&#xff1a;安装&#xff0c;获取数据&#xff0c;数据预处理 - 知乎 (zhihu.com) 一个实例讲解如何使用BP神经网络(附代码) - 知…

基于STM32单片机智能手环老人防跌倒报警系统GSM短信上报毕业设计7

STM32单片机老人防跌倒报警系统GSM短信上报加速度7 演示视频&#xff08;复制到浏览器打开&#xff09;&#xff1a; 基于STM32单片机的智能手环老人防跌倒报警系统设计GSM短信上报ADXL345重力加速度检测设计DIY开发板套件7 修改接收短信手机号码视频&#xff1a; ★★★如何修…

Pycharm解释器的配置: System Intgerpreter 、Pipenv Environment、Virtualenv Environment

文章目录 前提1. 环境准备2. 了解虚拟环境 一、进入Interpreter设置页二、添加Interpreter1. 方式一2. 方式二 三、 System Interpreter四、 Pipenv Environment前提条件&#xff1a;详细步骤1&#xff09; 选择pipenv2&#xff09; 设置Base Interpreter3&#xff09; 设置Pip…

必学的maven的起步

Maven的简介 maven是什么 Maven的本质是一个项目管理工具将项目开发和管理过程抽象成一个项目对象模型&#xff08;POM&#xff09;POM&#xff1a;项目对象模型 Maven的作用&#xff1a; 项目构建&#xff1a;提供标准的、跨平台的自动化项目构建方式依赖管理&#xff1a;方…

mingw下编译opencv4.5.2

初衷&#xff1a; 加载之前的模型没问题&#xff0c;但最近加载另一个模型时出现报错&#xff1a; OpenCV: terminate handler is called! The last OpenCV error is: OpenCV(4.1.0) Error: Assertion failed (nodesMapIt ! nodesMap.end()) in sortByExecutionOrder, file G…

R软件包ConsensusCluster进行共识聚类(Consensus Clustering)

从下面论文看到这个方法&#xff1a; Wang, Xin, et al. "Deep learning using bulk RNA-seq data expands cell landscape identification in tumor microenvironment." Oncoimmunology 11.1 (2022): 2043662. 这篇论文基于 AI 方法对 bulk RNA-seq 数据识别肿瘤微环…

qt项目-《图像标注软件》源码阅读笔记-Shape类绘图及其子类

目录 1. Shape 概览 2. Shape 基类 2.1 字段 2.2 方法 2.3 嵌套类型 3. Shape2D 2d形状纯虚基类 3.1 字段 3.2 方法 4. Shape3D 3d形状纯虚基类 5. Shape2D子类 5.1 Rectangle 矩形类 1. Shape 概览 功能&#xff1a;Shape类及其子类负责形状的绘制及形状的存储。…

【数据结构入门精讲 | 第十三篇】考研408、公司面试树专项练习(二)

在上一篇中我们进行了树的判断题、选择题、填空题专项练习&#xff0c;在这一篇中我们将进行编程题的相关练习。 目录 编程题R7-1 目录树R7-1 是否同一棵二叉搜索树R7-2 二叉搜索树的结构R7-3 平衡二叉树的根R7-1 完全二叉搜索树R7-1 修理牧场R7-2 嘴强王者R7-3 房屋分拆R7-4 动…