Go语言的Mutex

news2024/12/23 23:12:18

在本教程中,我们将了解互斥体。我们还将学习如何使用互斥体和通道解决竞争条件。

关键部分

在跳转到互斥体之前,了解并发编程中临界区的概念非常重要。当程序并发运行时,修改共享资源的代码部分不应被多个Goroutines同时访问。这部分修改共享资源的代码称为临界区。例如,假设有一段代码将变量 x 加 1。

x = x + 1  

只要上面的代码是由单个 Goroutine 访问的,就不会有任何问题。

让我们看看为什么当有多个 Goroutines 并发运行时这段代码会失败。为了简单起见,我们假设有 2 个 Goroutine 同时运行上面的代码行。

在内部,系统将按以下步骤执行上述代码行(还有更多涉及寄存器、加法工作原理等技术细节,但为了本教程,我们假设是下面三个步骤),

  1. 获取 x 的当前值
  2. 计算 x + 1
  3. 将步骤 2 中的计算值赋给 x

当这三个步骤仅由一个 Goroutine 执行时,一切都很好。

让我们讨论一下当 2 个 Goroutine 同时运行这段代码时会发生什么。下图描述了当两个 Goroutine 同时访问代码行时可能发生的一种情况x = x + 1

临界区

我们假设 x 的初始值为 0。Goroutine 1获取 x 的初始值0,计算 x + 1,在将计算值分配给 x 之前,系统上下文切换到Goroutine 2。现在Goroutine 2获取x其初始值为l 0,进行计算x + 1。之后,系统上下文再次切换到Goroutine 1。现在Goroutine 1将其计算值1赋给x,因此 x 变为 1。然后Goroutine 2再次开始执行,然后将其计算值1赋给x ,因此1x在个2Goroutine 执行之后的结果

现在让我们看看可能发生的不同情况。

临界区

在上述场景中,Goroutine 1开始执行并完成所有三个步骤,因此 x 的值变为1。然后Goroutine 2开始执行。此时 x的值为1,Goroutine 2执行完毕后, 的x值为2

所以从这两种情况,你可以看到x的最终值是12取决于上下文切换是如何发生的。这种程序的输出结果取决于 Goroutine 的执行顺序称为**竞争条件**。

在上述场景中,如果在任何时间点只允许一个 Goroutine 访问代码的关键部分,则可以避免竞争条件。这是通过使用互斥体来实现的。

Mutex

Mutex 用于提供一种锁定机制,以确保在任何时间点只有一个 Goroutine 运行代码的关键部分,以防止竞争条件的发生。

[sync](https://golang.org/pkg/sync/)包中可用。Mutex上定义了两种方法,即Lock和Unlock。调用之间存在的任何代码将仅由一个 Goroutine 执行,从而避免竞争条件

mutex.Lock()  
x = x + 1  
mutex.Unlock()  

在上面的代码中,x = x + 1在任何时间点都只会由一个 Goroutine 执行,从而防止竞争情况。

如果一个 Goroutine 已经持有锁,并且新的 Goroutine 正在尝试获取锁,则新的 Goroutine 将被阻塞,直到互斥锁被解锁。

具有竞争条件的程序

在本节中,我们将编写一个具有竞争条件的程序,在接下来的部分中,我们将修复竞争条件。

package main  
import (  
    "fmt"
    "sync"
    )
var x  = 0  
func increment(wg *sync.WaitGroup) {  
    x = x + 1
    wg.Done()
}
func main() {  
    var w sync.WaitGroup
    for i := 0; i < 1000; i++ {
        w.Add(1)        
        go increment(&w)
    }
    w.Wait()
    fmt.Println("final value of x", x)
}

在上面的程序中,increment的函数x增加1,然后调用WaitGroup的Done()通知其完成。

我们从第 10 行生成 1000 个Goroutine。这些 Goroutine 中的每一个都同时运行,当尝试x增加1 时,就会出现竞争条件。 因为多个 Goroutine 尝试同时访问 x 的值。

您可以看到由于竞争条件,每次的输出都会不同。我遇到的一些输出是

final value of x 981
final value of x 980

使用Mutex解决竞争条件

在上面的程序中,我们生成了 1000 个 Goroutines。如果每次将 x 的值增加 1,则 x 的最终所需值应为 1000。在本节中,我们将使用互斥体修复上述程序中的竞争条件。

package main  
import (  
    "fmt"
    "sync"
    )
var x  = 0  
func increment(wg *sync.WaitGroup, m *sync.Mutex) {  
    m.Lock()
    x = x + 1
    m.Unlock()
    wg.Done()   
}
func main() {  
    var w sync.WaitGroup
    var m sync.Mutex
    for i := 0; i < 1000; i++ {
        w.Add(1)        
        go increment(&w, &m)
    }
    w.Wait()
    fmt.Println("final value of x", x)
}

Run in playground

Mutex是一种结构类型,我们创建了一个Mutex类型的零值变量m。在上面的程序中,我们更改了increment函数,使 x 递增的代码x = x + 1位于m.Lock()m.Unlock()之间。现在这段代码没有任何竞争条件,因为在任何时间点都只允许一个 Goroutine 执行这段代码。

现在如果运行这个程序,它将输出

final value of x 1000  

在第 21 行传递互斥锁的地址非常重要。如果互斥量是按值传递而不是地址传递,则每个 Goroutine 将拥有自己的互斥量副本,并且竞争条件仍然会发生。

使用通道解决竞争条件

我们也可以使用通道来解决竞争条件。让我们看看这是如何完成的。

package main  
import (  
    "fmt"
    "sync"
    )
var x  = 0  
func increment(wg *sync.WaitGroup, ch chan bool) {  
    ch <- true
    x = x + 1
    <- ch
    wg.Done()   
}
func main() {  
    var w sync.WaitGroup
    ch := make(chan bool, 1)
    for i := 0; i < 1000; i++ {
        w.Add(1)        
        go increment(&w, ch)
    }
    w.Wait()
    fmt.Println("final value of x", x)
}

Run in playground

在上面的程序中,我们创建了一个容量为1的缓冲通道,并将其传递给increment。这个缓冲通道用于确保只有一个 Goroutine 访问递增 x 的代码的关键部分。通过缓冲通道传递true到完成的。冲通道的容量为1,所有其他试图写入该通道的 Goroutine 都会被阻塞,直到在第 1 3行读出。实际上,这仅允许一个 Goroutine 访问临界区。

该程序还打印

final value of x 1000  

Mutex与通道

我们已经使用Mutex和通道解决了竞争条件问题。那么我们如何决定何时使用什么?答案就在于你试图解决的问题。如果您尝试解决的问题更适合Mutex,那么请毫不犹豫地使用Mutex。如果问题似乎更适合渠道,那么就使用它。

大多数 Go 新手尝试使用通道来解决每个并发问题,因为这是该语言的一个很酷的功能。这是错误的。该语言为我们提供了使用 Mutex 或 Channel 的选项,选择任何一个都没有错误。

一般来说,当 Goroutine 需要相互通信时使用通道,而当只有一个 Goroutine 应该访问代码的关键部分时使用Mutex。

对于我们上面解决的问题,我更喜欢使用Mutex,因为这个问题不需要 goroutine 之间的任何通信。因此Mutex是一个很好的选择。

我的建议是选择适合问题的工具,而不是试图让问题适合该工具:

本教程到此结束。祝你有美好的一天。

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

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

相关文章

Delft3D水动力-富营养化模型实践技术高级应用

湖泊富营养化等水质问题严重威胁我国经济社会的发展&#xff0c;也是水环境和水生态领域科研热点之一。水环境模型是制定湖泊富营养化控制对策&#xff0c;预测湖泊水环境发展轨迹的重要工具&#xff0c;在环境影响评价、排污口论证等方面也有着广泛的应用。荷兰Delft研究所开发…

腾讯云轻量应用服务器性能如何?来自学生的评价

腾讯云轻量应用服务器怎么样&#xff1f;CPU性能如何&#xff1f;我们班同学人手一台&#xff0c;轻量服务器简单高效快速部署&#xff0c;不限制CPU性能&#xff0c;并且费用很低&#xff0c;很适合我们这种群生群体。可以的话&#xff0c;可以推出一些适合学生用户的GPU实例&…

二叉树问题——验证二叉搜索树

摘要 98. 验证二叉搜索树 一、验证二叉搜索树解析 给定一个二叉树&#xff0c;判断其是否是一个有效的二叉搜索树。假设一个二叉搜索树具有如下特征&#xff1a; 节点的左子树只包含小于当前节点的数。节点的右子树只包含大于当前节点的数。所有左子树和右子树自身必须也是…

何为自制力?如何提高自制力?

什么是自制力&#xff1f; 自制力也即是自我控制能力&#xff0c;是一个人如何去抵御外部诱惑力&#xff0c;从而坚持自己的原本计划&#xff0c;坚定去完成目标。除了外部诱惑力&#xff0c;也可以指的是面对困境&#xff0c;不良情绪等外部因素。 自制力是自我管理能力的体…

【Qt】文件系统

文章目录 文件系统文件操作案例&#xff1a;显示路径到标题框&#xff0c;显示内容到文本框对文件进行写操作获取文件相关信息 文件系统 Qt 通过QIODevice提供了对 I/O 设备的抽象&#xff0c;这些设备具有读写字节块的能力&#xff0c;下面是 I/O 设备的类图&#xff1a; QIO…

【Proteus仿真】【STM32单片机】智能助眠机系统设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用蜂鸣器闹铃模块、LCD1602显示模块、心率血氧模块、PCF8691 ADC模块、按键模块等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显示传…

【开源】基于SpringBoot的高校学院网站的设计和实现

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学院院系模块2.2 竞赛报名模块2.3 教育教学模块2.4 招生就业模块2.5 实时信息模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 学院院系表3.2.2 竞赛报名表3.2.3 教育教学表3.2.4 招生就业表3.2.5 实时信息表 四、系…

校园门禁升级改造,终于看到了希望!

随着社会的不断发展和技术的日益进步&#xff0c;安全和访问控制成为了各种场所和组织的首要关切。门禁监控系统作为一种融合了物理安全与电子监控的创新解决方案&#xff0c;已经成为了现代社会不可或缺的一部分。 客户案例 企业办公楼 福州某企业办公楼公司采用了泛地缘科技…

【开源】基于SpringBoot的农村物流配送系统的设计和实现

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统登录、注册界面2.2 系统功能2.2.1 快递信息管理&#xff1a;2.2.2 位置信息管理&#xff1a;2.2.3 配送人员分配&#xff1a;2.2.4 路线规划&#xff1a;2.2.5 个人中心&#xff1a;2.2.6 退换快递处理&#xff1a;…

Spring源码-3.Bean的后置处理器

Bean后置处理器与注解的关系 首先以一个没有添加额外的后置处理器来说明&#xff1a;️GenericApplicationContext GenericApplicationContext context new GenericApplicationContext();public class Bean1 {private static final Logger log LoggerFactory.getLogger(Bea…

工作流审批平台,可迁移,可快速开发审批单(源码)

前言 activiti工作流引擎项目&#xff0c;企业erp、oa、hr、crm等企事业办公系统轻松落地&#xff0c;请假审批demo从流程绘制到审批结束实例。 一、项目形式 springbootvueactiviti集成了activiti在线编辑器&#xff0c;流行的前后端分离部署开发模式&#xff0c;快速开发平…

vscode C++项目相对路径的问题

如图所示的项目目录结构 如果要在main.cpp里用相对路径保存一个txt文件 std::ofstream file("./tree_model/my_file.txt");if (file.is_open()) {file << "This is a sample text.\n";file.close();std::cout << "File saved in the mode…

stm32 Keil V5 开发pack突然丢失

1.好好的工程,前一天可以正常编译,第二天突然无法编译,显示缺少安装包文件,如是重新下载安装包进入导入。 2.显示丢失 1.做STM32开发时,经常发现下载的DEMO代码无法打开,ST自带的更新库软件根本连不上服务器,每次到此都非常恼火。 3. CMSIS变成可红色 2. 莫名其妙的丢…

通过servlet设计一个博客系统

博客系统 准备工作servlrt依赖mysql依赖jackson依赖 服务器和数据库的交互设计数据库/数据表封装DBUtil,实现建立连接和断开连接创建实体类bloguser 编写Dao类BlogDaoUserDao 前端和服务器的交互功能一:博客列表页约定格式后端代码前端代码 功能二:实现博客详情页约定格式后端代…

NCP1256ESN65T1G具有多种保护功能 一款低功率离线电流模式PWM控制器

NCP1256ESN65T1G 包括构建几瓦到几十瓦成本高效开关模式电源所需的一切功能。该零件采用微型 TSOP-6 封装&#xff0c;供电范围高达 30 V&#xff0c;具有带抖动的 65 kHz 或 100 kHz 开关电路&#xff0c;在峰值电流模式控制下运行。当辅助侧功率开始降低时&#xff0c;该控制…

ARP欺骗攻击

ARP协议的概念 地址解析协议&#xff0c;即ARP&#xff08;Address Resolution Protocol&#xff09;&#xff0c;是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到局域网络上的所有主机&#xff0c;并接收返回消息&#xff0c;以此…

邂逅React及React初体验

文章目录 一、React是什么二、React数据对比三、React特点3.1 技术特点3.2 声明式编程3.3 组件化开发3.4 多平台适配 四、React开发依赖4.1 React的开发依赖4.2 Babel和React的关系4.3 React的依赖引入 五、React初体验5.1 Hello World5.2 增加按钮&#xff0c;点击修改文本5.3…

多元数据库时代,如何选择数据库基础设施?

一 多元数据库时代趋势特点 在数据库技术发展、数字化转型应用和信创三大驱动力共同作用下&#xff0c;数据库从单一架构支持多类应用演变为多类架构支持多类应用。这些架构并非替代关系&#xff0c;而是相互共存、共同发展的关系&#xff0c;数据库的多样性成为必然&#xff0…

Oculus开发入门

老是访问官网搭建unity环境太麻烦了&#xff0c;自己记录一下&#xff0c;在国内看。 官网教程连接 Getting Started with Interaction SDK | Oculus Developers Adjust Camera Rig Once you’ve completed the tutorial listed above, you can find Interaction SDK in Unit…