Go锁 详解

news2024/9/21 10:41:11

- Go 函数并发编程中,锁是一种同步机制,用于协调对共享资源的访问,防止数据竞争
- Go 中提供了多种类型的锁,每种锁都有不同的特性和适用场景

类型

  • 互斥锁(mutex)

    • 基础锁,只能同时允许一个 goroutine 获取资源(悲观锁)
    • 保证了对共享资源的独占访问
    • 适用于对数据进行频繁写操作的场景
  • 读写锁(RWMutex)

    • 更高级的锁,它允许多个goroutine同时读取受保护的数据,但只允许一个goroutine同时写入(悲观锁)
    • 可以提高程序的性能,因为读取操作通常比写入操作要快
    • 适用于对数据进行频繁读操作的场景

互斥锁

  • 底层结构
// sync 包下的mutex就是互斥锁
type Mutex struct {
 state int32
 sema  uint32
}
- state:表示当前互斥锁的状态,复合型字段
- sema:信号量变量,用来控制等待goroutine的阻塞休眠和唤醒

state的不同位分别表示了不同的状态,使用最小的内存来表示更多的意义
在这里插入图片描述

// 其中低三位由低到高分别表示mutexLocked、mutexWoken 和 mutexStarving
// 剩下的位则用来表示当前共有多少个goroutine在等待锁:
const (
   mutexLocked = 1 << iota // 表示互斥锁的锁定状态
   mutexWoken // 表示从正常模式被从唤醒
   mutexStarving // 当前的互斥锁进入饥饿状态
   mutexWaiterShift = iota // 当前互斥锁上等待者的数量
)
提供了三个公开方法:
Lock():获得锁,Unlock():释放锁,在Go1.18新提供了TryLock()方法可以非阻塞式的取锁操作
  • 加锁
    加锁

  • 释放锁
    在这里插入图片描述

  • 正常模式(默认)

    • 采用公平的先进先出策略
    • 当一个goroutine尝试获取锁时,如果锁处于加锁状态,该goroutine会被放入等待队列中,等待锁的释放。当锁被解锁后,等待队列中的goroutine会按照先后顺序获取锁
    • 当一个协程被唤醒后并不是直接拥有锁,该协程需要和刚刚到达的协程一起竞争锁的所有权
    • 当等待的 goroutine 1ms内没有获取到锁,将会把锁置为饥饿模式
  • 饥饿模式

    • 非公平的模式
    • 互斥锁会直接交给等待队列最前面的goroutine,新的 goroutine 在该状态下不能获取锁、也不会进入自旋状态,它们只会在队列的末尾等待
    • 当等待队列中的协程获取到锁,它会查看以下俩个条件,有任意一个满足,将会将锁改为普通模式
      1. 自己是否是等待队列中最后一个协程
      2. 自己等待的时间是否小于1ms
  • 自旋

    • 定义:
      • 加锁时,如果发现该锁当前由其他协程持有,尝试加锁的协程并不是马上转入阻塞,而是会持续的探测锁是否被释放
      • 自旋时间很短,但如果在自旋过程中发现锁已被释放,那么协程可以立即获取锁
    • 优势:
      • 为了更加高效,减少损耗,更充分的利用CPU,尽量避免协程切换
      • 当前申请加锁的协程拥有CPU,如果经过短时间的自旋可以获得锁,当前协程可以继续运行,不必进入阻塞状态
    • 条件:
      • 自旋次数要足够小,通常为4,即自旋最多4次
      • CPU核数要大于1,否则自旋没有意义,因为此时不可能有其他协程释放锁
      • 调度机制中的Process数量要大于1,否则自旋没有意义
      • 调度机制中的可运行队列必须为空,否则会延迟协程调度,需要把CPU让给更需要的进程
    • 问题:
      • 自旋有个特性,无视正在排队等待加锁的进程,在自旋过程中,获取到锁便可加锁,类似于插队
      • 极端情况下,很多进程正排队等待加锁,此时有进程刚到,开始自旋加锁,如果成功,该进程便插队成功加锁。如果此时不断有进程自旋加锁,则在排队的进程将长时间无法获取到锁
      • 解决:锁添加饥饿状态,该状态下不允许自旋
  • 结论

    • 一般认为普通模式会有更好的性能,因为即使有等待的协程,新的协程可以连续获取到锁
    • 饥饿模式能够防止等待协程长时间获取不到锁。

读写锁

读写锁包含了两种锁:读锁、写锁,因此设计中两种锁的权重可能有下列三种场景:
- 读优先:读任务占有锁时,后续的读任务可以立即获得锁;这种设计可以提高并发性能(后来的读任务不需要等待),但如果读任务太多,会造成写任务一直处于等待中,造成写饥饿现象
- 写优先:指如果有写任务在等待锁,会阻塞后来的读任务获取锁。保证了写任务不会被持续的读进程阻塞,但如果写任务过多,又会导致读任务一直被阻塞,造成读任务饿死。
- 读写权重一致:读写锁的优先级一样,即普通的Mutex
Golang的读写实现中,采用了读优先、写优先交替策略:
  - 在读任务执行过程中,对于接收到的写任务、读任务,采取写优先策略,阻塞接收到的读任务,让写任务在读过程结束后优先执行
  - 在写任务执行过程中,对于接收到的写任务、读任务,采取读优先策略,阻塞接收到的写任务,让读任务在写过程结束后优先执行
  - 使用交替机制,确保不会因为读写任何一方任务过多,造成另一方的任务无法执行
  • 底层结构
type RWMutex struct {
 w           Mutex  // held if there are pending writers
 writerSem   uint32 // semaphore for writers to wait for completing readers
 readerSem   uint32 // semaphore for readers to wait for completing writers
 readerCount int32  // number of pending readers
 readerWait  int32  // number of departing readers
}

w:复用互斥锁提供的能力
writerSem:写操作goroutine阻塞等待信号量,当阻塞写操作的读操作goroutine释放读锁时,通过该信号量通知阻塞的写操作的goroutine
readerSem:读操作goroutine阻塞等待信号量,当写操作goroutine释放写锁时,通过该信号量通知阻塞的读操作的goroutine
redaerCount:当前正在执行的读操作goroutine数量
readerWait:当写操作被阻塞时等待的读操作goroutine个数
  • 获取读锁:

    • 获取读锁时,先将读计数器 readerCount 增1,表示增加一个读任务
    • 当readerCount值为负时,表示前面存在等待处理写任务或有写任务正在处理,此时阻塞新接收到的读任务,等待信号量通知
  • 释放读锁:

    • 释放读锁时,先将读计数器 readerCount 减一,表示完成一个读任务
    • 如果 readerCount 为负,则存在需要优先处理的写任务,进入慢路径
    • 首先检测读计数器的临界区,防止RUnlock调用出错(上锁一次、解锁多次)
    • 因为此时存在写任务,readerWait已被写任务赋值,将该值减一,表示写任务执行前要处理的读任务完成一个
    • 如果readerWait为0,则表示写任务执行之前的所有读任务都已完成,释放写信号量,执行等待处理的写任务
  • 获取写锁:

    • 获取写锁时,先抢占互斥锁;因为当存在多个写任务时,同一时间仅会处理一个
    • 反转readerCount的值为负,同时计算收到写任务时的读任务数量
    • 当读任务数量>0时,表示存在正在处理的读任务,将该值累加给readerWait,表示执行接收到的写任务时需要执行多少任务
    • 当readWait > 0,表示有任务要执行,因为通过信号量将写任务阻塞
  • 释放写锁:

    • 释放写锁时,先将readerCount反转为正值表示写任务执行完成,并计算读任务的数量;在释放写锁期间如果有新到的并发读任务,因为readerCount>=0,可以立即获取读锁执行
    • 释放r次读信号量,将在写任务期间被阻塞的读任务唤醒执行
    • 释放Mutex互斥锁

在这里插入图片描述

  • 总结
    • 读写锁提供四种操作:读上锁,读解锁,写上锁,写解锁;加锁规则是读读共享,写写互斥,读写互斥,写读互斥
    • 读写锁中的读锁是一定要存在的,其目的是也是为了规避原子性问题,只有写锁没有读锁的情况下会导致我们读取到中间值
    • Go语言的读写锁在设计上也避免了写锁饥饿

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

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

相关文章

【C++ Primer Plus习题】6.5

问题: 解答: #include <iostream> using namespace std;int main() {float salary 0;float tax 0;while (salary>0){cout << "请输入您的工资:";cin >> salary;if (cin.fail())break;if (salary < 5000){tax 0;}else if (salary < 15…

2024 MongoDB中国用户大会倒计时2天!请查收专属参会指南

距离2024 MongoDB中国用户大会即将开幕仅剩2天&#xff0c;我们非常期待与您共同探讨和分享最新的数据库技术与应用经验。为了确保您能够顺利参与本次会议&#xff0c;请查阅属于您的专属温馨提示&#xff01; 活动时间 8月31日09:00-17:30 签到开始&#xff1a;08:00 现场参…

Linux实现异步IO的方法:epoll,posix aio,libaio,io_uring

Linux中异步IO的实现方式大概有以下几种&#xff1a; 1. epoll 熟悉网络编程的人可能会想到select&#xff0c;poll&#xff0c;epoll这些异步IO的方式&#xff0c;但实际上这些方式叫做非阻塞IO&#xff0c;并不是实际意义上的异步IO。因此这些只能用于异步的Socket IO&…

Xline v0.7.0: 一个用于元数据管理的分布式KV存储

Xline是什么&#xff1f;我们为什么要做Xline&#xff1f; Xline是一个基于Curp协议的&#xff0c;用于管理元数据的分布式KV存储。现有的分布式KV存储大多采用Raft共识协议&#xff0c;需要两次RTT才能完成一次请求。当部署在单个数据中心时&#xff0c;节点之间的延迟较低&a…

【C++从小白到大牛】C++的隐式和显示类型转换基础知识讲解

目录 1、C语言中的类型转换 2、C语言和C中可以相互转换的类型总结 C语言&#xff1a; CPP&#xff1a; 3. 为什么C需要四种类型转换 4、C四大强制类型转换 4.1static_cast 4.2 reinterpret_cast 4.3 const_cast 4.4dynamic_cast 注…

吴恩达机器学习笔记 四十五 基于内容的过滤的tensorFlow实现

一个user网络&#xff0c;一个item网络 &#xff0c;使用顺序模型&#xff0c;激活函数选择relu&#xff0c;最后的输出大小都是32。 input_user提取特征&#xff0c;然后把这些特征送给上面的user_NN这个网络得到用户向量vu&#xff0c;再对vu进行标准化&#xff08;用l2范式&…

【最精简】解决访问GitHub慢的问题

我们通过命令行ping两个网站获取对应的IP地址 在windows中&#xff0c;修改hosts文件&#xff0c;路径一般是C:\Windows\System32\drivers\etc&#xff0c;用记事本打开&#xff0c;输入以下内容即可。 // 配置GitHub加速 199.232.69.194 github.global.ssl.fastly.net 140.8…

数据结构与算法 第3天(栈和队列)

栈和队列也是线性表&#xff0c;限制插入和删除的位置只能在端点 栈&#xff08;stack&#xff09; 后进先出 LIFO 表尾进入&#xff0c;表尾删除 一、案例 案例一&#xff1a;进制转换 例子 159转换成八进制 159/819...7 19/82...3 2/80...2 结果为237 案例二&#xff1a;括…

【香橙派系列教程】(二十) 系统移植、交叉编译工具链—OrangePi Zero2 SDK说明

【二十】基于OrangePi Zero2的系统移植—OrangePi Zero2 SDK说明 文章目录 【二十】基于OrangePi Zero2的系统移植—OrangePi Zero2 SDK说明1.使用环境要求2.Linux获取SDK3.首次编译完整SDK 之前我们在使用香橙派时&#xff0c; 都是直接在香橙派上进行代码编译&#xff0c; 但…

联华证券-新手炒股入门指南:学习路径与注意事项

学习炒股是一个循序渐进的过程&#xff0c;以下是入门建议以及需要注意的事项&#xff1a; 学习炒股的入手步骤 掌握基础知识&#xff1a; 股票市场基础&#xff1a;了解什么是股票、股市的运作机制、股票的种类等基本概念。 常用术语&#xff1a;熟悉如市盈率&#xff08;P/…

8.29c++

仿照string类&#xff0c;实现myString #include <iostream> #include <cstring> using namespace std; class myString { public://无参构造myString():size(10){strnew char[size];//堆区创建空间}//有参构造myString(const char *s){sizestrlen(s);//获取长度s…

TeamTalk消息服务器学习

msg_server发送消息 信令 //service id 0x0003 message IMMsgData{//cmd id: 0x0301required uint32 from_user_id 1; //消息发送方required uint32 to_session_id 2; //消息接受方required uint32 msg_id 3; // 非常重要&#xff1a;由谁产生&#xff1f;答&…

ShenNiusModularity项目源码学习(2:登录页面验证码)

前端登录页面Login.cshtml位于ShenNius.Admin.Mvc项目的Areas\Sys\Views\User内&#xff0c;页面中使用的验证码是调用同项目内UserController的OnGetVCode函数获取验证码图片。   点击验证码图片&#xff0c;会调用wwwroot\js\login.js定义的changeSrcCode函数刷新验证码。…

Qt调用外部exe并嵌入到Qt界面中(验证成功的成功)

http://t.csdnimg.cn/CDsqQ 原作者在这里 本文章主要介绍如何用Qt调用其他应用的exe,并将窗口嵌入到Qt界面中。很多人查到的代码都能成功的将exe调用起来&#xff0c;但是嵌入不到窗口中。主要有两种原因&#xff0c;现在从头到尾的梳理一下。 1.主要代码 1.1启动exe //包含…

keil编译输出的信息program size含义,以及个人使用中的编译内存溢出问题

keil编译后输出的信息含义 data对应的是片内的RAM&#xff0c;xdata对应的是程序中片外扩展的存储器上需要占用的容量&#xff0c;code是编写的程序占用单片机片内的存储程序ROM上的容量。 编译中发现错误 上图中的data值占用了147字节&#xff0c;超过了128字节。 一般解决…

2700+存储过程的超复杂Oracle,国产化怎么办?

前不久部门同事参与了一个关键客户的国产数据库Poc工作。从群里反馈的一些信息来看&#xff0c;难度还是比较大。 我们知道&#xff0c;对于数据库国产改造&#xff0c;其实有几个比较大的难点&#xff0c;其中最让人头疼的无非就是存储过程之类的对象了。 从同时提供的迁移报告…

掌握MySQL就差这一个——超详细讲解Mysql集群技术(包含主从复制,半同步模式,组复制,MHA)

一 Mysql 在服务器中的部署方法 在企业中 90% 的服务器操作系统均为 Linux 在企业中对于 Mysql 的安装通常用源码编译的方式来进行 官网&#xff1a; http://www.mysql.com 1.1 在Linux下部署mysql 1 安装依赖性&#xff1a; [rootmysql_node1 ~]# yum install cmake gcc-c…

django(二):第一个项目

接上文&#xff0c;django&#xff08;一&#xff09;&#xff1a;项目搭建开始开发第一个项目。 1. 新建app 创建一个app应用&#xff0c;取名为company。 python manage.py startapp company注册app到settings.py文件中。 2.实现Model层 settings.py文件中配置数据库&am…

软件测试学习笔记丨静态测试与代码审计 SonarQube

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/32049 一&#xff0c;SonarQube 平台搭建 1.1&#xff0c; 介绍 Sonar 是一个用于代码质量管理的开放平台。通过插件机制&#xff0c;Sonar 可以集成不同的测试工具、代码分析工具&#xff…

FATE Board 执行流程探索

背景介绍 FATE Board 是 FATE 提供的一个工程&#xff0c;用于给 FATE 提供可视化能力&#xff0c;方便在联邦学习训练中实时查看执行状态&#xff0c;更好地定位执行中遇到的问题。 查看 FATE 架构可以看到 FATE Board 是建立在 MySQL 和 FATE Flow Server 的基础上的&#…