Go-Mutex互斥量

先来看一段go1.12.5中Mutex的源码:

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.// Package sync provides basic synchronization primitives such as mutual
// exclusion locks. Other than the Once and WaitGroup types, most are intended
// for use by low-level library routines. Higher-level synchronization is
// better done via channels and communication.
//
// Values containing the types defined in this package should not be copied.
package syncimport ("internal/race""sync/atomic""unsafe"
)func throw(string) // provided by runtime// A Mutex is a mutual exclusion lock.
// The zero value for a Mutex is an unlocked mutex.
//
// A Mutex must not be copied after first use.
type Mutex struct {state int32sema  uint32
}// A Locker represents an object that can be locked and unlocked.
type Locker interface {Lock()Unlock()
}const (mutexLocked = 1 << iota // mutex is lockedmutexWokenmutexStarvingmutexWaiterShift = iota// Mutex fairness.//// Mutex can be in 2 modes of operations: normal and starvation.// In normal mode waiters are queued in FIFO order, but a woken up waiter// does not own the mutex and competes with new arriving goroutines over// the ownership. New arriving goroutines have an advantage -- they are// already running on CPU and there can be lots of them, so a woken up// waiter has good chances of losing. In such case it is queued at front// of the wait queue. If a waiter fails to acquire the mutex for more than 1ms,// it switches mutex to the starvation mode.//// In starvation mode ownership of the mutex is directly handed off from// the unlocking goroutine to the waiter at the front of the queue.// New arriving goroutines don't try to acquire the mutex even if it appears// to be unlocked, and don't try to spin. Instead they queue themselves at// the tail of the wait queue.//// If a waiter receives ownership of the mutex and sees that either// (1) it is the last waiter in the queue, or (2) it waited for less than 1 ms,// it switches mutex back to normal operation mode.//// Normal mode has considerably better performance as a goroutine can acquire// a mutex several times in a row even if there are blocked waiters.// Starvation mode is important to prevent pathological cases of tail latency.starvationThresholdNs = 1e6
)// Lock locks m.
// If the lock is already in use, the calling goroutine
// blocks until the mutex is available.
func (m *Mutex) Lock() {// Fast path: grab unlocked mutex.if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {if race.Enabled {race.Acquire(unsafe.Pointer(m))}return}var waitStartTime int64starving := falseawoke := falseiter := 0old := m.statefor {// Don't spin in starvation mode, ownership is handed off to waiters// so we won't be able to acquire the mutex anyway.if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {// Active spinning makes sense.// Try to set mutexWoken flag to inform Unlock// to not wake other blocked goroutines.if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {awoke = true}runtime_doSpin()iter++old = m.statecontinue}new := old// Don't try to acquire starving mutex, new arriving goroutines must queue.if old&mutexStarving == 0 {new |= mutexLocked}if old&(mutexLocked|mutexStarving) != 0 {new += 1 << mutexWaiterShift}// The current goroutine switches mutex to starvation mode.// But if the mutex is currently unlocked, don't do the switch.// Unlock expects that starving mutex has waiters, which will not// be true in this case.if starving && old&mutexLocked != 0 {new |= mutexStarving}if awoke {// The goroutine has been woken from sleep,// so we need to reset the flag in either case.if new&mutexWoken == 0 {throw("sync: inconsistent mutex state")}new &^= mutexWoken}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)starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNsold = m.stateif old&mutexStarving != 0 {// If this goroutine was woken and mutex is in starvation mode,// ownership was handed off to us but mutex is in somewhat// inconsistent state: mutexLocked is not set and we are still// accounted as waiter. Fix that.if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {throw("sync: inconsistent mutex state")}delta := int32(mutexLocked - 1<<mutexWaiterShift)if !starving || old>>mutexWaiterShift == 1 {// Exit starvation mode.// Critical to do it here and consider wait time.// Starvation mode is so inefficient, that two goroutines// can go lock-step infinitely once they switch mutex// to starvation mode.delta -= mutexStarving}atomic.AddInt32(&m.state, delta)break}awoke = trueiter = 0} else {old = m.state}}if race.Enabled {race.Acquire(unsafe.Pointer(m))}
}// Unlock unlocks m.
// It is a run-time error if m is not locked on entry to Unlock.
//
// A locked Mutex is not associated with a particular goroutine.
// It is allowed for one goroutine to lock a Mutex and then
// arrange for another goroutine to unlock it.
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+mutexLocked)&mutexLocked == 0 {throw("sync: unlock of unlocked mutex")}if new&mutexStarving == 0 {old := newfor {// If there are no waiters or a goroutine has already// been woken or grabbed the lock, no need to wake anyone.// In starvation mode ownership is directly handed off from unlocking// goroutine to the next waiter. We are not part of this chain,// since we did not observe mutexStarving when we unlocked the mutex above.// So get off the way.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)return}old = m.state}} else {// Starving mode: handoff mutex ownership to the next waiter.// Note: mutexLocked is not set, the waiter will set it after wakeup.// But mutex is still considered locked if mutexStarving is set,// so new coming goroutines won't acquire it.runtime_Semrelease(&m.sema, true)}
}
mutex.go

Mutex结构体

type Mutex struct {state int32      //互斥锁的状态sema  uint32    //信号量,协程阻塞等待该信号量,解锁的协程释放信号量从而唤醒等待信号量的协程。
}

查资料的时候找到一张很好的展示Mutex的内存布局的图:

  • Locked:表示该Mutex是否已被锁定,0代表未被锁定,1代表已被锁定。
  • Woken:表示是否有协程已被唤醒,0代表没有协程被唤醒,1代表已有协程被唤醒,正在加锁。
  • Starving:表示该Mutex是否处于饥饿状态,0代表不处于饥饿状态,1代表处于饥饿状态,即阻塞超过1ms。
  • Waiter:表示阻塞等待锁的协程个数,协程解锁时根据此值来判断是否需要释放信号量。

协程之间抢锁实际上是抢给Locked赋值的权利,能给Locked域置1,就说明抢锁成功。抢不到就阻塞等待Mutex.sema信号量,一旦持有锁的协程解锁,等待的协程会依次被唤醒。

(这个作者写的真好,原文链接我放到文末了~)

(有时候很恍惚,自己到底有没有写博客的必要,大部分时候都是互联网的搬运工,总有一天,我也能写出纯原创的技术博客~)

两种操作模式

go的互斥有两种操作模式:正常模式饥饿模式

在正常模式下,等待状态的协程(等待者)按照FIFO顺序排队。一个由沉睡(sleep)醒转(就绪)来的协程不拥有互斥锁,并且它对于互斥锁的竞争并不如新到来的协程有优势。新到来的协程可能已经在CPU上运行,并且可能数量还很多。但是,如果醒转来的等待者在队列中阻塞等待互斥锁超过1毫秒,正常模式将会切换到饥饿模式。

在饥饿模式下,互斥锁的所有权直接从释放锁的协程传递到队列最前的等待者。新到来的协程即使有竞争优势,也不会去争取互斥锁,相反,它们会到队列尾部排队等候。

如果醒转来的等待者获得互斥锁的所有权并且发现(1)它是队列中的最后一个等待者,或者(2)它等待不到1ms,它便会把饥饿模式切换到正常模式。

正常模式具有相当好的性能,因为即使存在阻塞的等待者,协程也可以连续多次获取互斥锁。

而饥饿模式可以预防到达协程迟迟得不到处理(反而可能被远远排在新协程之后),说白了,饥饿模式就是为防止协程进入饥饿状态忙等而设。

两种方法

Mutex只提供了两种方法,即Lock()加锁和Unlock()解锁。

加锁

以上面那张Mutex内存布局图为例,最简单毫无阻塞的加锁就是将Locked置为1,当锁已被占用,则将Waiter++,协程进入阻塞,直到Locked值变为0后被唤醒。

解锁

仍是以上面那张Mutex内存布局图为例,没有其他协程阻塞等待加锁(即Waiter为0),则只要将Locked置为0,不需要释放信号量。若解锁时,有1个或多个协程阻塞(即Waiter>0),则需要在将Locked置为0后,释放信号量唤醒阻塞的协程。

自旋

基本概念

上面我们说到,加锁时可能被阻塞,此时,协程并不是立即进入阻塞,而是会持续检测Locked是否变为0,这个过程即为自旋(spin)过程。从源码mutex源码中runtime_canSpin()和runtime_doSpin()两个方法,它们就是用来判断是否可以自旋(即是否符合自旋条件)和执行自旋的。

自旋必须满足一下所有条件:

  • 自旋次数要足够小,通常为4,即每个协程最多自旋4次。
  • CPU核数要大于1,否则自旋没有意义,因为此时不可能有其他协程释放锁。
  • 协程调度机制中的Process数量要大于1,比如使用GOMAXPROCS()将处理器设置为1就不能启动自旋。
  • 协程调度机制中的可运行队列必须为空,否则会延迟协程调度。

限制自旋次数很好理解,关于CPU核数,一开始我不理解为什么要限制这个,不妨来设想一下协程自旋的场景,假设一个协程主动释放了锁并释放了信号量,我们的协程在对处理器的竞争中惨败,因此进入短暂自旋,以期寻找其他门路,即看看其他处理器是不是正有协程准备解锁,试想,假如只有1核,刚刚在和我们的竞争中获取取得锁控制权的协程,怎么可能在短期内释放锁,因此只能直接进入阻塞。

至于什么是GOMAXPROCS呢?就是逻辑CPU数量,它可以被设置为如下几种数值:

  • <1:不修改任何数值。
  • =1:单核心执行。
  • >1:多核并发执行

一般情况下,可以使用runtime.NumCPU查询CPU数量,并使用runtime.GOMAXPROCS()进行设置,如:runtime.GOMAXPROCS(runtime.NumCPU),将逻辑CPU数量设置为物理CPU数量。

现在想想,对Process进行限制,是不是显而易见的事。

至于可运行队列为什么必须为空,我的理解,就是当前只能有这一条就绪线程,也就是说同时只能有一条自旋。

自旋的好处

可以更充分的利用CPU。

自旋的坏处

如果协程通过自旋获得锁,那么之前被阻塞的协程将无法获得锁,如果加锁的协程特别多,每次都通过自旋获得锁,那么之前被阻塞的协程将很难获得锁,从而进入饥饿状态。因此,在1.8版本以后,饥饿状态(即Starving为1)下不允许自旋。

补充

自旋和模式切换是有区别的,自旋发生在阻塞之前,模式切换发生在阻塞之后。

整个互斥过程是围绕着Mutex量进行的,即争夺对Mutex内存的修改权,Mutex可以看作是处理器的钥匙,争夺到Mutex的协程可以被处理。

Woken的作用:

Woken用于加锁和解锁过程的通信,譬如,同一时刻,有两个协程,一个在加锁,一个在解锁,在加锁的协程可能在自旋,此时把Woken置为1,通知解锁协程不必释放信号量了。也就是说Woken标志着当前有就绪状态的进程,不用解锁协程去通知。

参考链接

https://my.oschina.net/renhc/blog/2876211

另外还有一篇详解mutex源码的博客:

https://blog.csdn.net/qq_31967569/article/details/80987352

转载于:https://www.cnblogs.com/StrayLesley/p/10943889.html

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

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

相关文章

spss方差分析_【案例】SPSS统计分析:多因素方差分析

&#xff0d; 点击上方“中国统计网”订阅我吧&#xff01;&#xff0d;多因素方差分析&#xff0c;用于研究一个因变量是否受到多个自变量(也称为因素)的影响&#xff0c;它检验多个因素取值水平的不同组合之间&#xff0c;因变量的均值之间是否存在显著的差异。多因素方差分…

你好世界

想打个Hello World&#xff0c;但是又没有继续下去。 今天买茶百道的时候&#xff0c;做茶的女孩子很开朗很友善&#xff0c;在她递给我装好的山竹荔枝的时候我闻到水果的苦味&#xff0c;于是捧住杯子埋头下去闻了一下&#xff0c;她笑着说&#xff0c;“每次我做这个的时候都…

sqlserver免安装_SQL数据分析,如何免安装在线运行?

大家好&#xff0c;在之前写了一篇关于SQL软件安装&#xff0c;读者普遍反映&#xff0c;这个软件有点不好安装&#xff0c;但是&#xff0c;从事数据分析行业&#xff0c;熟练使用SQL软件是必须的&#xff0c;于是乎......本文推送一篇免安装的&#xff0c;可以在线运行的SQL软…

python自动导出数据脚本_利用python生成一个导出数据库的bat脚本文件的方法

# 环境: python3.xdef getExportDbSql(db, index):# 获取导出一个数据库实例的sql语句sql mysqldump -u%s -p%s -h%s -P%d --default-character-setutf8 --databases mu_ins_s%s > %s.s%d.mu_ins_%d.sql %(db[user], db[pwd], db[host], db[port], index, db[server], inde…

java内存模型和内存结构_Java内存模型和优化

java内存模型和内存结构总览 许多多线程代码开发人员都熟悉这样的想法&#xff0c;即不同的线程可以对持有的值有不同的看法&#xff0c;这不是唯一的原因&#xff0c;即如果线程不安全&#xff0c;它可能不会看到更改。 JIT本身可以发挥作用。 为什么不同的线程看到不同的值&…

ANTLR巨型教程

解析器是功能强大的工具&#xff0c;使用ANTLR&#xff0c;您可以编写可用于多种不同语言的各种解析器。 在本完整的教程中&#xff0c;我们将要&#xff1a; 解释基础 &#xff1a;解析器是什么&#xff0c;解析器可以用于什么 了解如何设置要从Javascript&#xff0c;Pyth…

Web进程被kill掉后线程还在运行怎么办?

目录 背景描述原因分析处理方案参考背景描述 系统有一个配置表&#xff0c;系统在启动后会启动一个线程&#xff0c;每隔5分钟将配置表里所有的数据更新到内存中。 系统是通过jenkins构建&#xff08;直接kill掉Web进程&#xff0c;然后传入新的包再启动&#xff09;的&#xf…

北京学python 价格怎么样_北京学习Python需要多少钱

Python本身语言具有一定的优势所在&#xff0c;简单易学&#xff0c;就业方向多&#xff0c;也就意外着从事Python的工程师相对于其他编程语言的工程师来说&#xff0c;就业机会更多&#xff0c;薪资待遇更高。1、web网站工程师&#xff1a;我们都知道web是我们一直不能忽视的存…

go Windows Service

相关库 https://godoc.org/golang.org/x/sys/windows/svchttps://github.com/kardianos/servicehttps://github.com/judwhite/go-svchttps://github.com/btcsuite/winsvc参考 云监控Go语言版本插件介绍 - 这个不知道用的什么库实现的Cannot start golang application exe as a …

华为南太无线解决方案部梁旭阳_华为无线充电新专利:激光无线充电,替代传统半接触式...

传统的无线充电技术是用感应线圈通过短距离&#xff0c;实际上是有接触式充电&#xff0c;但是相对于真正的无线充电&#xff0c;还是不够便利&#xff0c;最近&#xff0c;华为新无线充电技术曝光&#xff1a;激光无线充电。虽然无线充电已在近年得到了较大的普及&#xff0c;…

jquery的懒加载记录一下踩得坑

官网下载地址&#xff1a;http://plugins.jquery.com/lazyload/ 要引入两个文件&#xff0c;注意这些事html中包含着 <script src"https://code.jquery.com/jquery-1.11.3.min.js"></script><script src"js/jquery.lazyload.js"></sc…

rsync 同步优化_可以优化同步吗?

rsync 同步优化总览 有一个常见的误解&#xff0c;因为JIT很智能&#xff0c;并且可以消除对象的同步&#xff0c;而该对象仅存在于不影响性能的方法中。 比较StringBuffer和StringBuilder的测试 这两个类基本上做同样的事情&#xff0c;除了一个是同步的&#xff08;StringBu…

sudoers 用户权限配置_使用sudo让普通用户获取root用户的权限

sudo 是 Linux 系统管理指令&#xff0c;是允许系统管理员让普通用户执行一些或者全部的 root 命令的一个工具&#xff0c;如 halt&#xff0c;reboot&#xff0c;su 等等。这样不仅减少了 root 用户的登录 和管理时间&#xff0c;同样也提高了安全性。sudo不是对 shell 的一个…

Spring Boot –使用执行器端点在运行时配置日志级别

从Spring Boot 1.5开始&#xff0c;新的loggers器执行器端点允许在运行时查看和更改应用程序记录级别。 将spring-boot-actuator添加到您的项目 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator&…

Python学习教程:Python增强赋值及共享引用注意事项

Python学习教程&#xff1a;Python增强赋值及共享引用注意事项 概述 Python中的增强赋值是从C语言中借鉴出来的&#xff0c;所以这些格式的用法大多和C一致&#xff0c;本身就是对表达式的简写&#xff0c;即二元表达式和赋值语句的结合&#xff0c;比如a b 和a a b 就是一致…

python math.sinh_Python numpy.sinh()用法及代码示例

numpy.sinh(x [&#xff0c;out]) ufuncsin)&#xff1a;此数学函数可帮助用户计算所有x(作为数组元素)的双曲正弦值。等效于1/2 *(np.exp(x)-np.exp(-x))或-1j * np.sin(1j * x)。参数&#xff1a;array :[数组]元素以弧度为单位。2pi弧度 36o度返回&#xff1a;对于所有x即数…

中国大学MOOC-陈越、何钦铭-数据结构-2019春期末考试(题目+部分解答)

欢迎评论&#xff08;指正或是询问都可&#xff09;&#xff0c;谢谢大家 一、判断题 思路很正常的题目。 1-4反例&#xff1a;4个顶点只用3条边就可以做到全连通。所以边数可能等于顶点个数减一。错误。 二、选择题 2-8需要细心的做一下双旋操作&#xff0c; 2-12 C 设数字 {…

分析java 线程占用内存_Java线程:保留的内存分析

分析java 线程占用内存本文将为您提供一个教程&#xff0c;使您可以确定活动应用程序Java线程中保留了多少Java堆空间 。 将提供来自Oracle Weblogic 10.0生产环境的真实案例研究&#xff0c;以使您更好地理解分析过程。 我们还将尝试证明过多的垃圾回收或Java堆空间的内存占用…

图像傅里叶变换

1傅里叶变换物理意义 图像的频率是表征图像中灰度变化剧烈程度的指标&#xff0c;是灰度在平面空间上的梯度。如&#xff1a;大面积的沙漠在图像中是一片灰度变化缓慢的区域&#xff0c;对应的频率值很低&#xff1b;而对于地表属性变换剧烈的边缘区域在图像中是一片灰度变化剧…

python获取涨停股票_今日股市光大证券

一、查询股票账户的总盈亏的方法如下&#xff1a;1、查询你的银行银证转账记录&#xff0c;用转进的资金总额减去转出的资金总额&#xff0c;再和你现在的证券账户市值比较&#xff0c;即可得出盈亏。2、联系你的开户券商&#xff0c;通过柜面进行轧差查询资金进出情况&#xf…