Go 并发可视化解释 - sync.Mute

news2024/11/19 9:29:19

在学习 Go 编程语言时,您可能会遇到这句著名的格言:“不要通过共享内存来进行通信;相反,通过通信来共享内存。” 这句话构成了 Go 强大并发模型的基础,其中通道(channels)作为协程之间的主要通信工具。然而,虽然通道是管理并发的多功能工具,但错误地假设我们应该始终用通道替换传统的锁定机制,如 Mutex,是一个错误的观念。在某些情况下,使用 Mutex 不仅恰当,而且比通道更有效。

在我的 Go 并发可视化系列中,今天我将通过视觉方式来解释 sync.Mutex

Golang 基础

场景

想象一下,有四位 Gopher 自行车手每天骑车上班。他们都需要在到达办公室后洗个澡,但办公室只有一个浴室。为了防止混乱,他们确保一次只能有一个人使用浴室。这种独占式访问的概念正是 Go Mutex(互斥锁)的核心。

bf34c38a3f6fc9ab2d159ffe05b90bbd.png

每天早上在办公室洗澡对自行车手和跑步者来说是一个小小的竞争。

普通模式

今天最早到达的是 Stringer。当他来的时候,没有人在使用浴室,因此他可以立即使用浴室。

对一个未加锁的 Mutex 调用 Lock() 会立即成功。

片刻后,Partier 到了。Partier 发现有人在使用浴室,但他不知道是谁,也不知道什么时候会结束使用。此时,他有两个选择:站在浴室前面(主动等待),或者离开并稍后再回来(被动等待)。按 Go 的术语,前者被称为“自旋”(spinning)。自旋的协程会占用 CPU 资源,增加了在锁定可用时获取 Mutex 的机会,而无需进行昂贵的上下文切换。然而,如果 Mutex 不太可能很快可用,继续占用 CPU 资源会降低其他协程获取 CPU 时间的机会。

从版本 1.21 开始,Golang 允许到达的协程自旋一段时间。如果在指定时间内无法获取 Mutex,它将进入休眠状态,以便其他协程有机会运行。

14dea1bc70160aedd59d555282387957.png
 

到达的协程首先自旋,然后休眠。

Candier 到了。就像 Partier 一样,她试图获取浴室。

1713dbf0667141489a7a37329b5932b3.png
 

因为她刚到,如果 Stringer 很快释放浴室,她就有很大的机会在被动等待之前获取它。这被称为普通模式。

普通模式的性能要好得多,因为协程可以连续多次获取 Mutex,即使有阻塞的等待者。

802c169f355683c6be50192d2c2b88c1.png
1*GJ7OW0_8z_8QjXPa2cFxPw.png

go/src/sync/mutex.go at go1.21.0 · golang/go · GitHub[1]

新到达的协程在争夺所有权时具有优势

饥饿模式

Partier 回来了。由于他等待的时间很长(超过 1 毫秒),他将尝试以饥饿模式获取浴室。当 Swimmer 来时,他注意到有人饿了,他不会尝试获取浴室,也不会自旋。相反,他会排队在等待队列的尾部。

在这种饥饿模式下,当 Candier 结束时,她会直接把浴室交给 Partier。此时没有竞争。

b37df75b60e60bc216fc9153faa06bc4.png
 

饥饿模式是防止尾延迟的病理情况的重要措施。

1411210b8c3edaa0fa92edf479dbc3a9.png
 
7d4dfe9465324ba15876a95a37811c01.png

Partier 完成了他的回合并释放了浴室。此时,只有 Swimmer 在等待,因此他将立即拥有它。Swimmer 如果发现自己是最后一个等待的人,他会将 Mutex 设置回普通模式。如果他发现自己的等待时间少于 1 毫秒,也会这样做。

最后,Swimmer 在使用浴室后释放了它。请注意,Mutex 不会将所有者从“已锁定(由 Goroutine A 锁定)”状态更改为“已锁定(由 Goroutine B 锁定)”状态。它始终会在“已锁定”到“未锁定”然后再到“已锁定”的状态之间切换。出于简洁起见,上面的图像中省略了中间状态。

展示代码!

Mutex 的实现随时间而变化,实际上,要完全理解它的实现并不容易。幸运的是,我们不必完全理解其实现就能高效使用它。如果从这篇博客中只能记住一件事,那一定是:早到的人不一定赢得比赛。相反,新到达的协程通常具有更高的机会,因为它们仍在 CPU 上运行。Golang 还尝试避免通过实现饥饿模式来使等待者被饿死。

package main


import (
    "fmt"
    "sync"
    "time"
)


func main() {
    wg := sync.WaitGroup{}
    wg.Add(4)
    bathroom := sync.Mutex{}


    takeAShower := func(name string) {
        defer wg.Done()
        fmt.Printf("%s: I want to take a shower. I'm trying to acquire the bathroom\n", name)
        bathroom.Lock()
        fmt.Printf("%s: I have the bathroom now, taking a shower\n", name)
        time.Sleep(500 * time.Microsecond)
        fmt.Printf("%s: I'm done, I'm unlocking the bathroom\n", name)
        bathroom.Unlock()
    }


    go takeAShower("Partier")
    go takeAShower("Candier")
    go takeAShower("Stringer")
    go takeAShower("Swimmer")


    wg.Wait()
    fmt.Println("main: Everyone is Done. Shutting down...")
}

正如您可能猜到的,并发代码的结果几乎总是非确定性的。

第一次

Swimmer: I want to take a shower. I'm trying to acquire the bathroom

Partier: I want to take a shower. I'm trying to acquire the bathroom

Candier: I want to take a shower. I'm trying to acquire the bathroom

Stringer: I want to take a shower. I'm trying to acquire the bathroom

Swimmer: I have the bathroom now, taking a shower

Swimmer: I'm done, I'm unlocking the bathroom

Partier: I have the bathroom now, taking a shower

Partier: I'm done, I'm unlocking the bathroom

Candier: I have the bathroom now, taking a shower

Candier: I'm done, I'm unlocking the bathroom

Stringer: I have the bathroom now, taking a shower

Stringer: I'm done, I'm unlocking the bathroom

main: Everyone is Done. Shutting down...

第二次

Swimmer: I want to take a shower. I'm trying to acquire the bathroom

Swimmer: I have the bathroom now, taking a shower

Partier: I want to take a shower. I'm trying to acquire the bathroom

Stringer: I want to take a shower. I'm trying to acquire the bathroom

Candier: I want to take a shower. I'm trying to acquire the bathroom

Swimmer: I'm done, I'm unlocking the bathroom

Partier: I have the bathroom now, taking a shower

Partier: I'm done, I'm unlocking the bathroom

Stringer: I have the bathroom now, taking a shower

Stringer: I'm done, I'm unlocking the bathroom

Candier: I have the bathroom now, taking a shower

Candier: I'm done, I'm unlocking the bathroom

main: Everyone is Done. Shutting down...

自己实现 Mutex

实现 sync.Mutex 是困难的,但使用具有缓冲的通道来实现 Mutex 却相当容易。

type MyMutex struct {
    ch chan bool
}


func NewMyMutex() *MyMutex {
    return &MyMutex{
        // 缓冲大小必须为 1
        ch: make(chan bool, 1),
    }
}


// Lock 锁定 m。
// 如果锁已被使用,调用的协程将被阻塞,直到 Mutex 可用。
func (m *MyMutex) Lock() {
    [m.ch](http://m.ch) <- true
}


// Unlock 解锁 m。
func (m *MyMutex) Unlock() {
    <-m.ch
}

这篇文章通过生动的场景和可视化效果很好地解释了 Go 语言中 sync.Mutex 的工作原理,以及如何使用互斥锁来管理并发

相关系列文章

使用通信顺序进程(CSP)模型的 Go 语言通道

Go并发可视化解释 – select语句

以可视化方式解释 Go 并发 - 通道

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

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

相关文章

unity自己对象池的使用

unity出了自己的对象池 这里记录一下用法 命名空间就是这个 一般有两种用法&#xff0c;第一种是在using里面获取&#xff0c;脱离这个域就释放。第二种是在Get和Release配合使用 // This version will only be returned to the pool if we call Release on it.//只有使用Re…

【计算机基础】让我们重新认识一下Visual Stduio及其操作,知识点汇总!!

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

远程连接ubuntu的mysql服务报错10061的解决方案

远程连接ubuntu的mysql服务报错10061的解决方案 我的解决方案是&#xff1a; 首先&#xff1a;进入/etc/mysql/mysql.conf.d文件夹下面&#xff0c;编辑mysqld.cnf文件。如图所示&#xff1a; 然后&#xff1a;将bind-address那一行改成bind-address 0.0.0.0.如图所示&…

李宏毅hw-8,auto-encoder for anomaly_detection

一、查漏补缺、熟能生巧: 主要是mu均值 和 logvar对数标准差 std标准差的 处理方面不熟练 二、代码解读&#xff1a; 1.sample_code中提供了3种model&#xff1a;fcn_model 、 conv_model 和 vae_model: (1)fcn_model的结构非常好理解&#xff1a; 就是通过全连接层进行降维…

子比主题v7.4绕授权接口源码

子比主题7.4绕授权接口源码&#xff0c;仅包含api文件仅供学习参考&#xff01;正版子比主题7.4请到官方下载这里不提供。 使用方法: 1.搭建一个站点&#xff0c;绑定apizibl.com域名&#xff0c;并开启SSL (随便找个域名的证书就行) 2.上传以上源码&#xff0c;并配置好伪静…

LeetCode75-06:移动零

移动零 初版(bug) func moveZeroes(nums []int) {if len(nums) 1{return}// 设置两个指针fp1,fp2分别指向第一个元素和第二个元素&#xff0c;// 两种情况// 假如指针fp1指向元素为零// 1) *fp2!0&#xff0c;则交换。// 2) *fp20&#xff0c;则fp2后移,直至*fp2!0,与fp1交换…

【刷题笔记9.24】LeetCode:对称二叉树

LeetCode&#xff1a;对称二叉树 一、题目描述&#xff1a; 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 二、题目分析 题目&#xff1a;对称二叉树 1、p 指针和 q 指针一开始都指向这棵树的根 2、随后 p 右移时&#xff0c;q 左移&#xff0c;p 左移时&a…

从平均数到排名算法

平均数用更少的数字&#xff0c;概括一组数字。属于概述统计量、集中趋势测度、位置测度。中位数是第二常见的概述统计量。许多情况下比均值更合适。算术平均数是3中毕达哥拉斯平均数之一&#xff0c;另外两种毕达哥拉斯平均数是几何平均数和调和平均数。 算术平均 A M 1 n ∑…

Netty简介及简单客户端/服务端示例代码

什么是Netty&#xff1f; Netty是一个NIO客户机-服务器框架&#xff0c;它支持快速而容易地开发网络应用程序&#xff0c;如协议服务器和客户机。它大大简化和简化了网络编程&#xff0c;如TCP和UDP套接字服务器。 “快速简单”并不意味着生成的应用程序将遭受可维护性或性能问…

pycharm 让控制台里的链接可以点击

前言 如果细心就会发现pychram控制台里一些链接是可以点击的,另外一些不行,那么如果让输出的链接可以点击如何做呢? 解决 输出的i链接会在控制台里可以点击,并且点击会在本地直接打开 如果打印的是网址则可以直接点击 print(file:///{}.format(i))print(https://www.baid…

[架构之路-220]:与机器打交道VS与人打交道,计算机系统VS人体系统,计算机网络VS人类社会:架构、通信、语言、网络、智能、情感、生命

目录 前言&#xff1a; 一、计算机系统架构 VS 人体系统架构 1.1 相似之处 1.2 不同之处 1.3 人的心理与计算机软件 1.4 计算机系统层次 VS 人的层次模型 二、计算机通信 VS 人与人交流 2.1 计算机通信communication 2.2 人与人的交流communication 2.3 智商 VS 情商…

mybatis拦截器执行原理

title: “mybatis拦截器执行过程” createTime: 2021-12-08T12:19:5708:00 updateTime: 2021-12-08T12:19:5708:00 draft: false author: “ggball” tags: [“mybatis”] categories: [“java”] description: “mybatis拦截器执行过程” m[toc] mybatis的拦截器本人平时也很…

【数据结构】链表--单链表

目录 一 概念及结构 二 单链表的实现 1 包含接口(SList.h) 2 打印和创造节点&#xff08;扩容&#xff09;&#xff08;SList.c) 3 尾插&#xff08;SList.c) 4 头插&#xff08;SList.c) 5 尾删&#xff08;SList.c) 6 头删&#xff08;SList.c) 7 在pos前插入x&…

大数据从入门到精通(超详细版)之Hive的案例实战,ETL数据清洗!!!

前言 嗨&#xff0c;各位小伙伴&#xff0c;恭喜大家学习到这里&#xff0c;不知道关于大数据前面的知识遗忘程度怎么样了&#xff0c;又或者是对大数据后面的知识是否感兴趣&#xff0c;本文是《大数据从入门到精通&#xff08;超详细版&#xff09;》的一部分&#xff0c;小…

安全生产知识竞赛活动小程序界面分享

安全生产知识竞赛活动小程序界面分享

SCR截面速度、氨氮比等标准及相对标准偏差计算

SCR截面速度、氨氮比等标准及相对标准偏差计算 # -*- coding: utf-8 -*- """ 联系QQ:3123575367&#xff0c;专业SCR脱硝仿真。 Created on Wed Sep 20 20:40:30 2023 该程序用来处理fluent通过xyplot导出的数据&#xff0c;可计算标准偏差SD、相对标准偏差RSD…

linux 安装 wordpress

文章目录 linux 安装 wordpress1. wordpress 简介2. wordpress功能和特点3. 部署要求4. 环境搭建4.1 部署 nginx4.1.1 新增配置文件 4.2 部署 PHP74.2.1 查看当前版本4.2.2 YUM 安装 PHP74.2.3 查看 PHP 版本4.2.4 启动PHP-FPM4.2.5 修改配置文件4.2.6 重启服务 4.3 部署 mysql…

Spring事务2+银行转账拓展

前言 Transactional()内可以写的属性 一、Dao层的更新 public interface LogDao {//#{}符号取的是接口方法中的形参 now()方法直接调用的内置函数Insert("insert into log (info,createDate) values(#{info},now())")void log(String info); }二、LogService层…

【Element】通知 Notification

ElementUI 弹出通知 created() {const h this.$createElementconst that thisthis.$notify({onClose: function () {that.do()},type: warning,duration: 5000, // 5秒后隐藏offset: 0, // 距离顶部dangerouslyUseHTMLString: false, showClose: false,customClass: notify-…

索引(含B树、B+树)

1、索引&#xff08;index&#xff09; 索引是在数据库表的字段上添加的&#xff0c;是为了提高查询效率存在的一种机制。 一张表的一个字段可以添加一个索引&#xff0c;当然&#xff0c;多个字段联合起来也可以添加索引。 索引相当于一本书的目录&#xff0c;是为了缩小扫描…