go-sync-mutex

Sync

​ Go 语言作为一个原生支持用户态进程(Goroutine)的语言,当提到并发编程、多线程编程时,往往都离不开锁这一概念。锁是一种并发编程中的同步原语(Synchronization Primitives),它能保证多个 Goroutine 在访问同一片内存时不会出现竞争条件(Race condition)等问题。

通过atomic.CompareAndSwapInt32调用汇编CAS(compare and swap)指令的原子性来实现临界区的互斥访问,保证只有一个协程获取到锁

​ 当其中一个 goroutine 获得了这个锁,其他 goroutine 尝试获取这个锁时将会被阻塞,直到持有锁的 goroutine 释放锁为止。

​ Go 语言在 sync 包中提供了用于同步的一些基本原语,包括常见的 sync.Mutexsync.RWMutexsync.WaitGroupsync.Oncesync.Cond

!Mutex互斥锁

​ Go 语言的 sync.Mutex 由两个字段 statesema 组成。其中 state 表示当前互斥锁的状态,而 sema 是用于控制锁状态的信号量。

type Mutex struct {state int32sema uint32		// 指针地址 0xF,存着结构体的地址
}

Mutex.state

状态字段

int32类型的state代表:

  • locked: 锁状态 1被锁 0未被锁

  • woken:1是否有goroutine模式被唤醒,0未被唤醒

  • starving:1进入饥饿模式,0正常模式

  • 其他位:代表获取锁的等待队列中的协程数,state是int32类型,说明是32bit,其余位是32-3 bits,所以最大排队协程数就是2^(32-3)

锁模式

  • 正常模式:队头和新协程的抢占,未抢占到的扔到队尾
  • 饥饿模式:按顺序获取锁,不得插队,防止队尾一直阻塞等待
正常模式

在正常模式下获取锁:

  1. 多线程下竞争锁,获取成功返回,修改sync.Mutex结构体字段。获取失败,自旋等待其他线程释放锁,4次之后仍然拿不到锁,goroutine加入到等待队列尾部,状态改成_GWaiting
  2. 获取到锁的线程释放锁,从等待队列头部唤醒一个Goroutine,状态改成_Grunning,他会和新创建并且获取锁的新goroutine(M正在运行的g_Grunning)争抢锁。
    1. 如果被唤醒的G仍然未能抢到锁,goroutine加入到等待队列头部,状态改成_GWaiting
    2. 如果被唤醒的G抢到锁,新创建的G相当于重新进入1步骤
饥饿模式

在饥饿模式下获取锁:

互斥锁会直接交给等待队列最前面的 Goroutine。新的 Goroutine 在该状态下不能获取锁、也不会进入自旋状态,它们只会在队列的末尾等待。如果一个 Goroutine 获得了互斥锁并且它在队列的末尾或者它等待的时间少于 1ms,那么当前的互斥锁就会切换回正常模式。

锁模型切换
  • 正常模式切换到饥饿模式:被唤醒的 Goroutine 超过 1ms 没有获取到锁,它就会将当前互斥锁切换饥饿模式,防止部分 Goroutine 被『饿死』。
  • 饥饿模式换到正常模式切:
    • 一个 Goroutine 获得了互斥锁并且它在队列的末尾,说明没有协程在竞争了,切换到正常模式
    • 被唤醒的 Goroutine 获得锁没超过 1ms ,切换到正常模式

Mutex.Sema

控制锁状态的信号量(互斥信号量)

// runtime/sema.go
type semaRoot struct {lock  mutextreap *sudog // 锁抢占者的 平衡树的根nwait uint32 // 抢占锁的goroutine的数量
}

互斥锁加锁/解锁

  • func (m *Mutex) Lock():Lock方法锁住m,如果m已经加锁,则阻塞直到m解锁。
func (m *Mutex) Lock() {// 未锁状态,获取锁returnif atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {if race.Enabled {race.Acquire(unsafe.Pointer(m))}return}// Slow path (outlined so that the fast path can be inlined)m.lockSlow()
}func (m *Mutex) lockSlow() {var waitStartTime int64	// 协程抢占锁时间,时间超出,锁变成饥饿模式starving := falseawoke := falseiter := 0old := m.statefor {// 锁住状态下 and 不是饥模式 and 在可自旋次数下 进入if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {// awoke标记是false and 锁非唤醒状态 and 锁的等待者大于0  // 满足这些条件,把锁变成唤醒状态// awoke flag标记成trueif !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {awoke = true}// 自旋 汇编runtime_doSpin()// 累计自选次数iter++// 把唤醒状态 覆盖 oldold = m.statecontinue}// 可能其他协程更改了锁状态:改成了`未锁住状态` // 以下操作就有AB两种情况// A情况: 锁住状态 且 饥饿模式 (自旋次数超过4次)// B情况: 未锁住//拿到最新锁状态new := old// old不是饥饿模式(排除A情况),那是B情况,把new设置成锁状态if old&mutexStarving == 0 {new |= mutexLocked}// old 是 锁住状态 或 是饥饿模式。// 等待数+1 (当前协程加入等待)if old&(mutexLocked|mutexStarving) != 0 {new += 1 << mutexWaiterShift}// 饥饿标识非空 and old是锁住状态。 (第一次进入 且 A情况)// new设置成饥饿状态if starving && old&mutexLocked != 0 {new |= mutexStarving}// awoke标识是 唤醒状态if awoke {// new不是唤醒状态,锁标识不对,panicif new&mutexWoken == 0 {throw("sync: inconsistent mutex state")}// &^ 想异的位保留,相同的位清0。 非唤醒状态 变成 唤醒, 唤醒状态下变成非唤醒new &^= mutexWoken}// 此时new的3个字段状态 : 锁住,饥饿,唤醒状态未知// 如果状态没有被其他协程改变,状态更改成newif atomic.CompareAndSwapInt32(&m.state, old, new) {// 如果状态是非锁住 and 非饥饿模式 // compareAndSwapInt32已经改成锁住,break forif old&(mutexLocked|mutexStarving) == 0 {break // locked the mutex with CAS}// 设置排队者的开始等待时间queueLifo := waitStartTime != 0if waitStartTime == 0 {waitStartTime = runtime_nanotime()}// 信号量设置,阻塞等待(信号量的P操作,协程间通信)runtime_SemacquireMutex(&m.sema, queueLifo, 1)// 标记 饥饿标识, 如果是饥饿标识是true 或者 大于饥饿阈值 starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs// 获取最新锁状态,虽然前面compareAndSwap已经改成了m.state : 锁住,饥饿,唤醒状态未知。但是前面阻塞有可能其他协程更改了状态old = m.state// 锁是饥饿模式if old&mutexStarving != 0 {// 锁是 锁住状态 或者 唤醒状态 或者 等待者为0个时// 抛出if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {throw("sync: inconsistent mutex state")}// delta := int32(mutexLocked - 1<<mutexWaiterShift)// 非贪婪模式 或则 等待者为1时if !starving || old>>mutexWaiterShift == 1 {delta -= mutexStarving}atomic.AddInt32(&m.state, delta)break}awoke = trueiter = 0} else {old = m.state}}if race.Enabled {race.Acquire(unsafe.Pointer(m))}
}
  • func (m *Mutex) Unlock():Unlock方法解锁m,如果m未加锁会导致运行时错误。锁和线程无关,可以由不同的线程加锁和解锁。
func (m *Mutex) Unlock() {if race.Enabled {_ = m.staterace.Release(unsafe.Pointer(m))}// Fast path: drop lock bit.new := atomic.AddInt32(&m.state, -mutexLocked)if new != 0 {// Outlined slow path to allow inlining the fast path.// To hide unlockSlow during tracing we skip one extra frame when tracing GoUnblock.m.unlockSlow(new)}
}func (m *Mutex) unlockSlow(new int32) {if (new+mutexLocked)&mutexLocked == 0 {throw("sync: unlock of unlocked mutex")}if new&mutexStarving == 0 {old := newfor {if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {return}// Grab the right to wake someone.new = (old - 1<<mutexWaiterShift) | mutexWokenif atomic.CompareAndSwapInt32(&m.state, old, new) {runtime_Semrelease(&m.sema, false, 1)return}old = m.state}} else {// 信号量中的V操作runtime_Semrelease(&m.sema, true, 1)}
}

信号量:信号量有两种原子操作,他们必须成对出现
P操作:信号量 减1,当信号量 <0 ,表明资源被占用,进程阻塞。 当信号量>=0,表明资源被释放(可用),进程可继续执行
V操作:信号量加1,当信号量<=0时,代表有阻塞中进程。当信号量>0,表明没有阻塞中进程,无需操作
互斥信号量,默认值为1
————————————————
版权声明:本文为CSDN博主「我是你的小阿磊」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qiu18610714529/article/details/109062176

example

import "sync"func main() {m := sync.Mutex{}go user1(&m)go user2(&m)signalChan := make(chan os.Signal, 1)signal.Notify(signalChan, os.Interrupt)select {case <-signalChan:fmt.Println("catch interrupt signal")break}
}func printer(str string, m *sync.Mutex) {m.Lock()         //加锁defer m.Unlock() //解锁for _, ch := range str {fmt.Printf("%c", ch)time.Sleep(time.Millisecond * 1)}
}
func user1(m *sync.Mutex) {printer("hello ", m)
}
func user2(m *sync.Mutex) {printer("world", m)
}//打印结果
worldhello 或者 helloworld: 两个单词是有序的,不像`heworllldo`两个协程同时打印,说明某个协程会在mutex.Lock()进行自旋等待获取锁

RWMutex读写互斥锁

读写互斥锁 sync.RWMutex 是细粒度的互斥锁,它不限制资源的并发读,但是读写、写写操作无法并行执行。

type RWMutex struct {w           Mutex  // held if there are pending writerswriterSem   uint32 // semaphore for writers to wait for completing readersreaderSem   uint32 // semaphore for readers to wait for completing writersreaderCount int32  // number of pending readersreaderWait  int32  // number of departing readers
}
  • w — 复用互斥锁提供的能力;
  • writerSemreaderSem — 分别用于写等待读和读等待写:
  • readerCount 存储了当前正在执行的读操作数量;
  • readerWait 表示当写操作被阻塞时等待的读操作个数;

加锁/解锁

  • func (rw *RWMutex) RLock() :读加锁,如果有写锁,则阻塞等待

    func (rw *RWMutex) RLock() {if race.Enabled {_ = rw.w.staterace.Disable()}if atomic.AddInt32(&rw.readerCount, 1) < 0 {// 阻塞,等待信号量的v操作释放共享内存,才能获得执行权runtime_SemacquireMutex(&rw.readerSem, false, 0)}if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer(&rw.readerSem))}
    }
    
  • func (rw *RWMutex) RUnlock():解读锁,

    func (rw *RWMutex) RUnlock() {if race.Enabled {_ = rw.w.staterace.ReleaseMerge(unsafe.Pointer(&rw.writerSem))race.Disable()}if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {// Outlined slow-path to allow the fast-path to be inlinedrw.rUnlockSlow(r)}if race.Enabled {race.Enable()}
    }func (rw *RWMutex) rUnlockSlow(r int32) {if r+1 == 0 || r+1 == -rwmutexMaxReaders {race.Enable()throw("sync: RUnlock of unlocked RWMutex")}// A writer is pending.if atomic.AddInt32(&rw.readerWait, -1) == 0 {// The last reader unblocks the writer.runtime_Semrelease(&rw.writerSem, false, 1)}
    }
    
  • func (rw *RWMutex) Lock(): 写锁,如果有读写锁被占用,阻塞等待所有读写锁释放后才能获得

    • 其他 Goroutine 在获取写锁时会进入自旋或者休眠
    • 有其他 Goroutine 持有互斥锁的读锁该 Goroutine 会调用 runtime.sync_runtime_SemacquireMutex 进入休眠状态等待所有读锁所有者执行结束后释放 writerSem 信号量将当前协程唤醒;
func (rw *RWMutex) Lock() {if race.Enabled {_ = rw.w.staterace.Disable()}// First, resolve competition with other writers.rw.w.Lock()// Announce to readers there is a pending writer.r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders// Wait for active readers.if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {runtime_SemacquireMutex(&rw.writerSem, false, 0)}if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer(&rw.readerSem))race.Acquire(unsafe.Pointer(&rw.writerSem))}
}

example

func RMutex() {ch := make(chan struct{})rw := &sync.RWMutex{}go func() {rw.RLock()time.Sleep(time.Second * 5)defer rw.RUnlock()fmt.Println("fun1")}()go func() {time.Sleep(time.Millisecond * 500)rw.Lock()defer rw.Unlock()fmt.Println("fun2")close(ch)}()<-ch
}// 先打印出fun1 再打印fun2 代表了读写互斥

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python图像处理 ——图像分块

python图像处理 ——图像分块 前言一、分块与合并1.读取原始图像2.网格划分&#xff0c;将图像划分为m*n块3.网格合并 二、代码 前言 根据图像尺寸创建一个 ( m 1 ) ( n 1 ) 个均匀的网格顶点坐标&#xff0c;对于图像块来说每个图像块的左上角和右下角可以唯一确定一个图像…

0X02

web9 阐释一波密码&#xff0c;依然没有什么 发现&#xff0c;要不扫一下&#xff0c;或者看一看可不可以去爆破密码 就先扫了看看&#xff0c;发现robots.txt 访问看看,出现不允许被访问的目录 还是继续尝试访问看看 就可以下载源码&#xff0c;看看源码 <?php $fl…

【音视频 | Ogg】RFC3533 :Ogg封装格式版本 0(The Ogg Encapsulation Format Version 0)

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

双目视觉检测 KX02-SY1000型测宽仪 有效修正和消除距离变化对测量的影响

双目视觉检测的基本原理 利用相机测量宽度时&#xff0c;由于单个相机在成像时存在“近大远小”的现象&#xff0c;并且单靠摄入的图像无法知道被测物的距离&#xff0c;所以由被测物的跳动导致的被测物到工业相机之间距离变化&#xff0c;使测量精度难以提高。 因此测宽仪需…

React基础知识02

一、通过属性来传值&#xff08;props&#xff09; react中可以使用属性&#xff08;props&#xff09;可以传递给子组件&#xff0c;子组件可以使用这些属性值来控制其行为和呈现输出。 例子&#xff1a; // 1.1 父组件 import React, { useState } from react // 1.2引入子…

Rust编程基础之6大数据类型

1.Rust数据类型 在 Rust 中, 每一个值都属于某一个 数据类型&#xff08;data type&#xff09;, 这告诉 Rust 它被指定为何种数据&#xff0c;以便明确数据处理方式。我们将看到两类数据类型子集&#xff1a;标量&#xff08;scalar&#xff09;和复合&#xff08;compound&a…

多态 多继承的虚表深度剖析 (3)

&#x1f4af; 博客内容&#xff1a;多态 &#x1f600; 作  者&#xff1a;陈大大陈 &#x1f680; 个人简介&#xff1a;一个正在努力学技术的准C后端工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎私信&#xff01; &#x1f496; 欢迎大家&#xff1a;这里是CSD…

NLP之Bert多分类实现案例(数据获取与处理)

文章目录 1. 代码解读1.1 代码展示1.2 流程介绍1.3 debug的方式逐行介绍 3. 知识点 1. 代码解读 1.1 代码展示 import json import numpy as np from tqdm import tqdmbert_model "bert-base-chinese"from transformers import AutoTokenizertokenizer AutoToken…

Open3D(C++) 最小二乘拟合平面(间接平差法)

目录 一、算法原理1、原理概述2、参考文献二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。 一、算法原理 1、原理概述 通过传统最小二乘法对点云数据进行平面拟合时,可将误差只归因于一个方向上,本文假设误差只存在于 Z Z

Monarch Mixer:一种性能比Transformer更强的网络架构

六年前&#xff0c;谷歌团队在arXiv上发表了革命性的论文《Attention is all you need》。作为一种优势的机器学习网络架构&#xff0c;Transformer技术迅速席卷全球。Transformer一直是现代基础模型背后的主力架构&#xff0c;并且在不同的应用程序中取得了令人印象深刻的成功…

tbh着色

在tbh中&#xff0c;着色之前&#xff0c;首先可以可以创建多个色板&#xff0c;如果不同角色颜色不一样&#xff0c;就可以创建多个色板&#xff0c;每一个色板代表的角色不同。 1、创建色板 有两种方式&#xff1a; 方法一&#xff1a;在颜色菜单中&#xff0c;点击左上角 …

SQL面试

#(1)请写出要查询员工J开头的名字其工号(EMPNO)及部门名称(DEPTNA)的 SQL语句SELECT e.emp,e.name,d.deptna FROM emp e left join dept d on d.deptno e.deptno where e.name like J%#(2)请写出要查询 Kevin 所在部门的部门代号(DEPTNO)及部门名称(DEPTNA)的 SQL 语句SELECT e…

D-Link管理系统默认账号密码

默认口令为 admin:admin 登陆成功 文笔生疏&#xff0c;措辞浅薄&#xff0c;望各位大佬不吝赐教&#xff0c;万分感谢。 免责声明&#xff1a;由于传播或利用此文所提供的信息、技术或方法而造成的任何直接或间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c; 文章…

【RabbitMQ】RabbitMQ 集群的搭建 —— 基于 Docker 搭建 RabbitMQ 的普通集群,镜像集群以及仲裁队列

文章目录 一、集群分类1.1 普通模式1.2 镜像模式1.3 仲裁队列 二、普通集群2.1 目标集群2.2 获取 Erlang Cookie2.3 集群配置2.4 启动集群2.5 测试集群 三、镜像模式3.1 镜像模式的特征3.2 镜像模式的配置3.2.1 exactly 模式3.2.2 all 模式3.2.3 nodes 模式 3.3 测试镜像模式 四…

使用lua-resty-request库编写爬虫IP实现数据抓取

目录 一、lua-resty-request库介绍 二、使用lua-resty-request库进行IP数据抓取 1、获取IP地址 2、设置请求 3、处理数据 三、代码实现 四、注意事项 五、总结 本文将深入探讨如何使用lua-resty-request库在爬虫程序中实现IP数据抓取。我们将首先介绍lua-resty-request…

windows自动登陆

新建文本粘贴下面代码&#xff0c;另存为注册表文件 Windows Registry Editor Version 5.00[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Driver Signing] "Policy"hex:00[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]"DefaultUserN…

UI设计感蓝色商务数据后台网站模板源码

蓝色商务数据后台网站模板是一款适合网站模板下载。提示&#xff1a;本模板调用到谷歌字体库&#xff0c;可能会出现页面打开比较缓慢。 演示下载 qnziyw点cn/wysc/qdmb/20852点html

K8S部署时IP问题

本次环境搭建需要安装三台Centos服务器&#xff08;一主二从&#xff09;&#xff1b;搭配的前提时做好ip的设置 主机IP规划 IP地址的设定需要根据自己主机来设置&#xff0c;在虚拟机的虚拟网络编辑器中看他给你的ip&#xff1b;不要查什么ipconfig了。 在虚拟网络编辑器中…

基于SSM的社区智慧养老监护管理平台

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

回顾十大数据恢复软件,帮助用于恢复丢失的文件!

您是否因丢失计算机上的重要文件而感到恐慌&#xff1f;你不是一个人&#xff01;数据丢失是许多人面临的严重问题&#xff0c;但幸运的是&#xff0c;有许多解决方案可以恢复数据。 在本文中&#xff0c;我将回顾十大数据恢复软件&#xff0c;以帮助您恢复丢失的文件&#xf…