攀枝花住房和城乡建设厅官方网站/成都今天重大新闻事件

攀枝花住房和城乡建设厅官方网站,成都今天重大新闻事件,湖北网站建设平台,化工企业网站模板 aspx1、锁的概念引入 首先,为什么需要锁? 在并发编程中,多个线程或进程可能同时访问和修改同一个共享资源(例如变量、数据结构、文件)等,若不引入合适的同步机制,会引发以下问题: 数据竞…

1、锁的概念引入

首先,为什么需要锁?

在并发编程中,多个线程或进程可能同时访问和修改同一个共享资源(例如变量、数据结构、文件)等,若不引入合适的同步机制,会引发以下问题:

  • 数据竞争:多个线程同时修改一个资源,最终的结果跟线程的执行顺序有关,结果是不可预测的。

  • 数据不一致:一个线程在修改资源,而另一个线程读取了未修改完的数据,从而导致读取了错误的数据。

  • 资源竞争:多线程竞争同一个资源,浪费系统的性能。

因此,我们需要一把锁,来保证同一时间只有一个人能写数据,确保共享资源在并发访问下的正确性和一致性。

在这里,引入两种常见的并发控制处理机制,即乐观锁悲观锁

  • 乐观锁:假定在并发操作中,资源的抢占并不是很激烈,数据被修改的可能性不是很大,那这时候就不需要对共享资源区进行加锁再操作,而是先修改了数据,最终来判断数据有没有被修改,没有被修改则提交修改指,否则重试。

  • 悲观锁:与乐观锁相反,它假设场景的资源竞争激烈,对共享资源区的访问必须要求持有锁。

针对不同的场景需要采取因地制宜的策略,比较乐观锁与悲观所,它们的优缺点显而易见:

2、Sync.Mutex

Go对单机锁的实现,考虑了实际环境中协程对资源竞争程度的变化,制定了一套锁升级的过程。具体方案如下:

  • 首先采取乐观的态度,Goroutine会保持自旋态,通过CAS操作尝试获取锁。

  • 当多次获取失败,将会由乐观态度转入悲观态度,判定当前并发资源竞争程度剧烈,进入阻塞态等待被唤醒。

乐观转向悲观的判定规则如下,满足其中之一即发生转变:

  • Goroutine自旋尝试次数超过4次

  • 当前P的执行队列中存在等待被执行的G(避免自旋影响GMP调度性能)

  • CPU是单核的(其他Goroutine执行不了,自旋无意义)

除此之外,为了防止被阻塞的协程等待过长时间也没有获取到锁,导致用户的整体体验下降,引入了饥饿的概念:

  • 饥饿态:若Goroutine被阻塞等待的时间>1ms,则这个协程被视为处于饥饿状态

  • 饥饿模式:表示当前锁是否处于特定的模式,在该模式下,锁的交接是公平的,按顺序交给等待最久的协程。

饥饿模式与正常模式的转变规则如下:

  • 普通模式->饥饿模式:存在阻塞的协程,阻塞时间超过1ms

  • 饥饿模式->普通模式:阻塞队列清空,亦或者获得锁的协程的等待时间小于1ms,则恢复

接下来步入源码,观看具体的实现。

2.1、数据结构

位于包sync/mutex.go中,对锁的定义如下:

type Mutex struct {state int32sema  uint32
}

  • state:标识目前锁的状态信息,包括了是否处于饥饿模式、是否存在唤醒的阻塞协程、是否上锁、以及处于等待锁的协程个数有多少。

  • seme:用于阻塞和唤醒协程的信号量。

state看作一个二进制字符串,它存储信息的规则如下:

  • 第一位标识是否处于上锁,0表示否,1表示上锁(mutexLocked)

  • 第二位标识是否存在唤醒的阻塞协程(mutexWoken)

  • 第三位标识是否处于饥饿模式(mutexStarving)

  • 从第四位开始,记录了处于阻塞态的协程个数

const (mutexLocked = 1 << iota // mutex is lockedmutexWokenmutexStarvingmutexWaiterShift = iotastarvationThresholdNs = 1e6 //饥饿阈值
)

2.2、获取锁Lock()

func (m *Mutex) Lock() {if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {return}m.lockSlow()
}

尝试直接通过CAS操作直接获取锁,若成功则返回,否则说明锁被获取,步入LockSlow

2.3、LockSlow()

源码较长,进行拆分讲解:

var waitStartTime int64starving := falseawoke := falseiter := 0old := m.state

(1)定义了基本的常量,含义如下:

  • waitStartTime:记录当前协程等待的时间,只有被阻塞才会使用

  • awoke:标识当前协程是否被Unlock唤醒

  • iter:记录当前协程自旋尝试次数

  • old:记录旧的锁的状态信息

for {//处于上锁状态,并且不处于饥饿状态中,并且当前的协程允许继续自旋下去if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {awoke = true}runtime_doSpin()iter++old = m.statecontinue}//...}

(2)进入尝试获取锁的循环中,两个if表示:

  • 若锁处于上锁状态,并且不处于饥饿状态中,并且当前的协程允许继续自旋下去(非单核CPU、自旋次数<=4、调度器P的本地队列不存在等待执行的G),则步入:若当前协程并非从等待队列唤醒、并且不存在被唤醒的等待协程、并且存在位于阻塞的协程、则尝试设置mutexWoken标识为1,若成功:标识当前的协程为被唤醒的协程。(虽然并非实际从阻塞中唤醒)告诉P,当前的协程处于自旋态更新iter计数器,与old记录的当前锁的状态信息,进行下一次重试循环

这里存在的唯一疑惑为,为什么要将awoke标识为true?

首先,因为当前锁并非处于饥饿模式,因此当前的抢占锁的模式是不公平的,若当前锁的阻塞队列还没有被唤醒的协程,那就要求不要唤醒了,尝试让当前正在尝试的协程获取到锁,避免唤醒协程进行资源竞争。

for {//...new := oldif old&mutexStarving == 0 {new |= mutexLocked}if old&(mutexLocked|mutexStarving) != 0 {new += 1 << mutexWaiterShift}if starving && old&mutexLocked != 0 {new |= mutexStarving}if awoke {new &^= mutexWoken}//...
}		

(3)进行状态更新:

当协程从步骤2走出来时,只能说明它位于以下两个状态之一:

  • 旋不动了,或者锁进入饥饿模式了,锁要让给别人了,总之是获取不到锁了(悲观)。

  • 锁被释放了。

不论如何,都需要进行一些状态的更新,为接下来的打算做准备。

用new存储一个锁即将要进入的新状态信息,更新规则:

  • 若锁不处于饥饿模式:说明锁可能被释放了,也可能是自旋次数过多,不管接下来是否能拿到锁,锁都会被某一个协程获取,因此置mutexLocked为1。

  • 若锁可能处于饥饿状态,或者锁没有被释放:那说明自己是抢不到锁了,即将进入阻塞态,阻塞协程计数器+1。

  • 若当前的协程是被唤醒的,并且已经处在饥饿态中而且锁仍然锁着:锁进入绝对公平的饥饿模式

  • 若当前协程是被唤醒的:清除mutexWoken标识位,因为接下来可能需要有协程被唤醒(饥饿模式)。

虽然更新的有点多,但是可以归纳为

  • 若锁释放了,那就标识一下接下来锁要被获取即可。

  • 若锁没有释放,并给当前协程等待了很久,那锁就进入饥饿状态,接下来需要有阻塞协程被唤醒。


(4)尝试更新信息:

if atomic.CompareAndSwapInt32(&m.state, old, new) {//...} else {old = m.state}

接下来尝试将new更新进state,若更新失败,说明当前有另一个协程介入了,为了防止数据的一致性丢失,要全部重来一次。

(5)状态更新成功,具体判断是要沉睡还是获取锁成功

步入步骤4的if主支中,此时有两个状态:

if atomic.CompareAndSwapInt32(&m.state, old, new) {if old&(mutexLocked|mutexStarving) == 0 {break // locked the mutex with CAS}//...} else {//...}

因为当前状态,可能是锁释放了,检查锁更新前是否已经被释放了并且不是饥饿模式,若是那说明获取锁成功了,函数结束了。

if atomic.CompareAndSwapInt32(&m.state, old, new) {if old&(mutexLocked|mutexStarving) == 0 {break // locked the mutex with CAS}// If we were already waiting before, queue at the front of the queue.queueLifo := waitStartTime != 0if waitStartTime == 0 {waitStartTime = runtime_nanotime()}runtime_SemacquireMutex(&m.sema, queueLifo, 2)//....} else {//...}

否则,说明当前协程要进入阻塞态了,记录一下开始阻塞的时间,用于醒来是判断是否饥饿。然后进入阻塞沉睡中。

(6)若步骤5进入阻塞,则被唤醒后:

if atomic.CompareAndSwapInt32(&m.state, old, new) {if old&(mutexLocked|mutexStarving) == 0 {break // locked the mutex with CAS}// If we were already waiting before, queue at the front of the queue.queueLifo := waitStartTime != 0if waitStartTime == 0 {waitStartTime = runtime_nanotime()}runtime_SemacquireMutex(&m.sema, queueLifo, 2)//唤醒starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNsold = m.state//若锁处于饥饿模式if old&mutexStarving != 0 {//锁的异常处理if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {throw("sync: inconsistent mutex state")}//将要更新的信号量delta := int32(mutexLocked - 1<<mutexWaiterShift)if !starving || old>>mutexWaiterShift == 1 {delta -= mutexStarving}atomic.AddInt32(&m.state, delta)break}awoke = trueiter = 0//....} else {//...}

从阻塞中唤醒,首先计算一些协程的阻塞时间,以及当前的最新锁状态。

锁处于饥饿模式:那么当前协程将直接获取锁,当前协程是因为饥饿模式被唤醒的,不存在其他协程抢占锁。于是更新信号量,将记录阻塞协程数-1,将锁的上锁态置1。若当前从饥饿模式唤醒的协程,等待时间已经不到1ms了或者是最后一个等待的协程,那么将将锁从饥饿模式转化为正常模式。至此,获取成功,退出函数。

否则,只是普通的随机唤醒,于是开始尝试进行抢占,回到步骤1。

2.4、释放锁Unlock()

func (m *Mutex) Unlock() {//直接释放锁new := atomic.AddInt32(&m.state, -mutexLocked)if new != 0 {m.unlockSlow(new)}
}

通过原子操作,直接将锁的mutexLocked标识置为0。若置0后,锁的状态不为0,那就说明存在需要获取锁的协程,步入unlockSlow

2.5、unlockSlow()

func (m *Mutex) unlockSlow(new int32) {if (new+mutexLocked)&mutexLocked == 0 {fatal("sync: unlock of unlocked mutex")}if new&mutexStarving == 0 {old := newfor {if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {return}new = (old - 1<<mutexWaiterShift) | mutexWokenif atomic.CompareAndSwapInt32(&m.state, old, new) {runtime_Semrelease(&m.sema, false, 2)return}old = m.state}} else {runtime_Semrelease(&m.sema, true, 2)}
}

(1)首先进行了异常状态处理,若释放了一个已经释放了到锁,那么直接fatal,程序终止。

if (new+mutexLocked)&mutexLocked == 0 {fatal("sync: unlock of unlocked mutex")}

(2)若锁不处于饥饿状态:

  • 若此时的等待协程数量为0,或者锁被上锁了、含有被唤醒的协程、锁处于饥饿模式:都说明有新的协程介入了流程,已经完成了交接,可以直接退出

  • 唤醒一个处于阻塞态的协程。

否则,处于饥饿状态,唤醒等待最久的协程。

3、Sync.RWMutex

对于共享资源区的操作,可以划分为读与写两大类。假设在一个场景中,对共享资源区继续读的操作远大于写的操作,如果每个协程的读操作都需要获取互斥锁,这带来的性能损耗是非常大的。

RWMutex是一个可以运用在读操作>写操作中的提高性能的锁,可以将它视为由一个读锁与一个写锁构成。其运作规则具体如下:

  • 读锁允许多个读协程同时读取共享资源区,若有协程需要修改资源区的数据,那么它需要被阻塞。

  • 写锁具有严格的排他性,当共享资源区被上了写锁时,任何其他goroutine都不得访问

可见在最坏的情况下,所有的协程都是需要写操作时,读写锁会退化成普通的Mutex。

3.1、数据结构

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 atomic.Int32 // number of pending readersreaderWait  atomic.Int32 // number of departing readers
}
const rwmutexMaxReaders = 1 << 30 //最大的读协程数量

  • w:一个互斥的写锁

  • writerSem:关联被阻塞的写协程的信号量

  • readerSem:关联被阻塞的读协程的信号量

  • readerCount:正常情况下,记录正在读取的协程数量;但若当前是写协程正在持有锁,那么实际记录读协程的数量为readerCount - rwmutexMaxReader

  • readerWait:记录释放下一个写协程,还需要等待读协程完成的数量

3.2、读锁流程RLock()

func (rw *RWMutex) RLock() {if rw.readerCount.Add(1) < 0 {// A writer is pending, wait for it.runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)}
}

readerCount+1,表示新加入一个读协程。若结果<0,说明当前锁正在被写协程占据,令当前的读协程阻塞。

3.3、读释放锁流程RUnlock()

func (rw *RWMutex) RUnlock() {if r := rw.readerCount.Add(-1); r < 0 {// Outlined slow-path to allow the fast-path to be inlinedrw.rUnlockSlow(r)}
}

readerCount-1,表示减少一个读协程。若结果<0,说明当前锁正在被写协程占据,步入runlockslow。

3.4、rUnlockSlow()

func (rw *RWMutex) rUnlockSlow(r int32) {if r+1 == 0 || r+1 == -rwmutexMaxReaders {race.Enable()fatal("sync: RUnlock of unlocked RWMutex")}if rw.readerWait.Add(-1) == 0 {// The last reader unblocks the writer.runtime_Semrelease(&rw.writerSem, false, 1)}
}

首先进行错误处理,若发现当前协程为占用过读锁,或者读流程的协程数量上限,系统出现异常,fatal。

否则,对readerWait-1,若结果为0,说明当前协程是最后一个介入读锁流程的协程,此时需要释放一个写锁。

3.5、写锁流程Lock()

func (rw *RWMutex) Lock() {// First, resolve competition with other writers.rw.w.Lock()// Announce to readers there is a pending writer.r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders// Wait for active readers.if r != 0 && rw.readerWait.Add(r) != 0 {runtime_SemacquireRWMutex(&rw.writerSem, false, 0)}
}

首先尝试获取写锁,若获取成功,需要将readerCount-最大读协程数,表示现在锁被读协程占据。

r表示处于读流程的协程数量,若r不为0,那么就将readerWait加上r,等这些读协程都读取完毕,再去写。将这个写协程阻塞。(读写锁并非读、写公平,读协程优先。

3.6、写释放锁流程Unlock()

func (rw *RWMutex) Unlock() {// Announce to readers there is no active writer.r := rw.readerCount.Add(rwmutexMaxReaders)if r >= rwmutexMaxReaders {race.Enable()fatal("sync: Unlock of unlocked RWMutex")}// Unblock blocked readers, if any.for i := 0; i < int(r); i++ {runtime_Semrelease(&rw.readerSem, false, 0)}// Allow other writers to proceed.rw.w.Unlock()
}

重新将readerCount置为正常指,表示释放了写锁。若读协程超过最大上限,则异常。

然后唤醒所有阻塞的读协程。(读协程优先

解锁。

文章转载自:MelonTe

原文链接:golang单机锁实现 - MelonTe - 博客园

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

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

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

相关文章

【HarmonyOS Next】鸿蒙应用实现弹框DialogHub详解

【HarmonyOS Next】鸿蒙应用实现弹框DialogHub详解 一、前言 鸿蒙中实现弹框目前官方提供openCustomDialog和CustomDialog两种模式。推荐前者&#xff0c;详情见下图和官网文档链接&#xff1a; https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V14/arkts-u…

机器学习算法实战——天气数据分析(主页有源码)

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​ ​​​ 1. 引言 天气数据分析是气象学和数据科学交叉领域的一个重要研究方向。随着大数据技术的发展&#xff0c;气象数据的采集、存储和分…

炫酷的3D按钮效果实现 - CSS3高级特性应用

炫酷的3D按钮效果实现 - CSS3高级特性应用 这里写目录标题 炫酷的3D按钮效果实现 - CSS3高级特性应用项目介绍核心技术实现1. 基础结构设计2. 视觉效果实现2.1 背景渐变2.2 立体感营造 3. 交互动效设计3.1 悬停效果3.2 按压效果 技术要点分析1. 深度层次感2. 动画过渡3. 性能优…

解决python配置文件类configparser.ConfigParser,插入、读取数据,自动转为小写的问题

配置类 [Section1] Key_AAA Value[Section2] AnotherKey Value默认情况下&#xff0c;ConfigParser会将ini配置文件中的KEY&#xff0c;转为小写。 重载后配置类&#xff1a; 继承类从configparser.ConfigParser改为configparser.RawConfigParser重载方法optionxform&#…

微服务的网关配置

微服务的网关配置 1. 网关路由 1.1 网关 1.1.1 存在问题 单体架构时我们只需要完成一次用户登录、身份校验&#xff0c;就可以在所有业务中获取到用户信息。而微服务拆分后&#xff0c;每个微服务都独立部署&#xff0c;这就存在一些问题&#xff1a;每个微服务都需要编写身…

区间震荡指标

区间震荡指标的逻辑如下&#xff1a; 一、函数注解 1. Summation函数 功能&#xff1a; 计算给定价格序列Price的前Length个数据点的和&#xff0c;或在数据点数量超过Length时&#xff0c;计算滚动窗口内的价格和。 参数&#xff1a; Price(1)&#xff1a;价格序列&#…

C语言-数组指针和指针数组

指针 数组指针与指针数组 数组指针 定义 概念&#xff1a;数组指针是指向数组的指针&#xff0c;本质上还是指针 特点&#xff1a; ①先有数组&#xff0c;后有指针 ②它指向的是一个完整的数组 一维数组指针 语法&#xff1a; 数据类型 (*指针变量名)[容量]; 案例&a…

31天Python入门——第5天:循环那些事儿

你好&#xff0c;我是安然无虞。 文章目录 1. while循环1.1 while循环的嵌套1.2 补充学习:print函数 2. for循环2.1 range函数2.2 for循环2.3 continue和break以及return2.4 for循环的嵌套 3. 补充学习3.1 enumerate函数3.2 zip函数3.3 不要在遍历列表的过程中删除元素 循环 是…

c#难点整理

1.何为托管代码&#xff0c;何为非托管代码 托管代码就是.net框架下的代码 非托管代码&#xff0c;就是非.net框架下的代码 2.委托的关键知识点 将方法作为参数进行传递 3.多维数组 4.锯齿数组 5.多播委托的使用 6.is运算符 相当于逻辑运算符是 7.as 起到转换的作用 8.可…

数据结构之栈的2种实现方式(顺序栈+链栈,附带C语言完整实现源码)

对于逻辑关系为“一对一”的数据&#xff0c;除了用顺序表和链表存储外&#xff0c;还可以用栈结构存储。 栈是一种“特殊”的线性存储结构&#xff0c;它的特殊之处体现在以下两个地方&#xff1a; 1、元素进栈和出栈的操作只能从一端完成&#xff0c;另一端是封闭的&#xf…

Jmeter旧版本如何下载

1.Jmeter最新版本下载位置 https://jmeter.apache.org/download_jmeter.cgi2.Jmeter旧版本下载位置 https://archive.apache.org/dist/jmeter/binaries稳定版本&#xff1a;5.4.1

css-grid布局

文章目录 1、布局2、网格轨道3、间距Gap4、网格线5、网格别名 当一个 HTML 元素将 display 属性设置为 grid 或 inline-grid 后&#xff0c;它就变成了一个网格容器&#xff0c;这个元素的所有直系子元素将成为网格元素。 1、布局 启用grid布局类似与flex布局&#xff0c;不过g…

SolidWorks使用显卡教程

操作步骤&#xff1a; 打开注册表编辑器 按下键盘上的 Win R 组合键&#xff0c;输入 regedit 并按回车键&#xff0c;打开注册表编辑器。 导航到显卡信息路径 在注册表中依次展开以下路径&#xff1a; plaintext HKEY_CURRENT_USER\Software\SolidWorks\SOLIDWORKS 2021\Per…

【C++11】左值引用、右值引用、移动语义和完美转发

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:C ⚙️操作环境:Visual Studio 2022 目录 &#x1f4cc;左值引用和右值引用 &#x1f38f;左值和左值引用 &#x1f38f;右值和右值引用 &#x1f4cc;左值引用和右值引用比较 &#x1f38f;左值引用 &#x1f38f;右值…

【机密计算顶会解读】11:ACAI——使用 Arm 机密计算架构保护加速器执行

导读&#xff1a;本文介绍ACAI&#xff0c;其构建一个基于CCA的解决方案&#xff0c;使得机密虚拟机能够安全地使用加速器&#xff0c;同时保持与现有应用程序的兼容性和安全性&#xff0c;能够实现对加速器的安全访问。 原文链接&#xff1a;ACAI: Protecting Accelerator Ex…

【Java SE】抽象类/方法、模板设计模式

目录 1.抽象类/方法 1.1 基本介绍 1.2 语法格式 1.3 使用细节 2. 模板设计模式&#xff08;抽象类使用场景&#xff09; 2.1 基本介绍 2.2 具体例子 1.抽象类/方法 1.1 基本介绍 ① 当父类的某些方法&#xff0c;需要声明&#xff0c;但是又不确定如何实现时&#xff…

深度学习:从零开始的DeepSeek-R1-Distill有监督微调训练实战(SFT)

原文链接&#xff1a;从零开始的DeepSeek微调训练实战&#xff08;SFT&#xff09; 微调参考示例&#xff1a;由unsloth官方提供https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Qwen2.5_(7B)-Alpaca.ipynbhttps://colab.research.google.com/git…

流畅如丝:利用requestAnimationFrame优化你的Web动画体验

requestAnimationFrame 是前端开发中用于优化动画性能的 API。它允许浏览器在下一次重绘之前执行指定的回调函数&#xff0c;通常用于实现平滑的动画效果。 1.作用 优化性能&#xff1a;requestAnimationFrame 会根据浏览器的刷新率&#xff08;通常是 60Hz&#xff0c;即每秒…

【pytest框架源码分析五】pytest插件的注册流程

前文介绍到pytest整体是运用插件来实现其运行流程的。这里仔细介绍下具体过程。 首先进入main方法 def main(args: list[str] | os.PathLike[str] | None None,plugins: Sequence[str | _PluggyPlugin] | None None, ) -> int | ExitCode:"""Perform an i…

IoTDB日志提示Too many open files

问题 时序数据库 IoTDB 1.3.3 版本 IoTDB 执行查询操作失败&#xff0c;日志打印提示 Too many open files。通过命令查看打开文件数&#xff0c;结果如下&#xff1a; [root0002 DataReceiver]# lsof|grep 28347|wc -l DataNode 55444 [root0002 DataReceiver]# lsof|g…