go源码阅读 - sync/mutex
Mutex是go标准库中的互斥锁,用于处理并发场景下共享资源的访问冲突问题。
1. Mutex定义:
// 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 int32 //表示当前锁的状态
sema uint32 //信号量专用,用于阻塞/唤醒goroutine
} // A Locker represents an object that can be locked and unlocked.
type Locker interface {
Lock()
Unlock()
}
state字段在这里有更丰富的含义,他是一个32位的整型。他的多重含义通过不同的位来表示:
在这里,
locked:表示这个锁是否被持有。
wokend:表示是否有唤醒的goroutine线程。
starving:表示是否处于饥饿状态。
waiterCount:表示等待该锁的goroutine的数量。
2. 加锁操作
// 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.
// 如果当前处于未加锁状态,则直接加锁,并返回
// CompareAndSwapINt32() 将指定的old值 0 与指定地址 &m.state中的值相比较,如果相等,将new值 mutexLocked赋值给指定地址 &m.state。
if 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)
// 否则,进入slow path
m.lockSlow()
}
func (m *Mutex) lockSlow() {
// 等待时间
var waitStartTime int64
// 是否处于饥饿状态
starving := false
// 是否有唤醒的goroutine
awoke := false
// 自旋迭代次数
iter := 0
old := m.state
for {
// Don't spin in starvation mode, ownership is handed off to waiters
// so we won't be able to acquire the mutex anyway.
// 如果当前锁被占有且处于非饥饿状态,就进入自旋;自旋次数达到上限,再进入下一步。需要注意的是,如果这是一个被唤醒的协程,还需要将唤醒标志位置成唤醒状态1.
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.state //再次获取锁的状态,之后会检查锁是否被释放了
continue
}
new := old
// Don't try to acquire starving mutex, new arriving goroutines must queue.
// 自旋结束后,检查旧状态;如果是非饥饿状态,新的状态持有锁标志位置成 1
if old&mutexStarving == 0 {
new |= mutexLocked
}
// 检查旧状态,如果锁被占用或处于饥饿状态为1,waiter数量 +1;这个时候,协程只能进入等待队列。如果当前是饥饿状态,新来得协程什么也做不了,会直接沉睡。
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.
// 如果当前协程被唤醒后,认定是饥饿状态,并且目前锁是被占用状态;那么锁的饥饿标志位被设定为1.
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.
// 协程被唤醒,需要将唤醒标志位清0,因为他要么继续沉睡,要么获取锁
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 != 0
if waitStartTime == 0 {
waitStartTime = runtime_nanotime()
}
// 阻塞等待,休眠当前goroutine等待并尝试获取信号量唤醒当前goroutine
runtime_SemacquireMutex(&m.sema, queueLifo, 1)
// 唤醒之后检查锁是否处于饥饿状态
starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
old = m.state
// 如果锁已经处于饥饿状态,直接抢到锁,返回
if 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")
}
// 加锁,并将waiter数减1
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.
// 最后一个 waiter或者已经不饥饿了,清楚饥饿标记
delta -= mutexStarving
}
atomic.AddInt32(&m.state, delta)
break
}
awoke = true
iter = 0
} else {
old = m.state
}
} if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
}
加锁操作如下:
1. 新来的goroutine直接抢占锁,抢占失败进入自旋(循环),自旋一定次数后还是没有抢到锁进入等待队列。这一步给占有CPU时间片的goroutine更多的机会,降低整体的性能消耗。
2. 当前协程被唤醒后,判断是否处于饥饿状态;
如果是非饥饿状态,那么和新来的协程走正常抢占锁的流程;因为被唤醒的协程不占用cpu时间片,经常抢不过新来的协程;如果协程超过1ms还没有获取到锁,就进入饥饿状态。
如果是饥饿状态,将锁的饥饿标志位设置为1;锁的持有者执行完任务后,会将饥饿状态的锁交给队列中的第一个协程;新来的协程不会去抢占锁,也不会spin,而会直接进入阻塞队列。
3. 解锁操作
// 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.state
race.Release(unsafe.Pointer(m))
} // Fast path: drop lock bit.
// 给 m.state直接减 1, 如果等于0,表示没有其他goroutine在等待,可以直接返回。
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.
// 否则,需要通过信号量机制唤醒沉睡的goroutine
m.unlockSlow(new)
}
} func (m *Mutex) unlockSlow(new int32) {
// 解锁一个没有锁定的mutex会报错
if (new+mutexLocked)&mutexLocked == 0 {
throw("sync: unlock of unlocked mutex")
}
// 判断是否处于饥饿状态,如果不处于饥饿状态
if new&mutexStarving == 0 {
old := new
for {
// 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.
// 如果没有等待的goroutine,或者goroutine已经被唤醒或持有了锁,直接返回
if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
return
}
// Grab the right to wake someone.
// 唤醒等待者,并将唤醒goroutine的唤醒标志位设置为 1
new = (old - 1<<mutexWaiterShift) | mutexWoken
if atomic.CompareAndSwapInt32(&m.state, old, new) {
runtime_Semrelease(&m.sema, false, 1)
return
}
old = m.state
}
} else {
// Starving mode: handoff mutex ownership to the next waiter, and yield
// our time slice so that the next waiter can start to run immediately.
// 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.
// 如果处于饥饿状态,则直接唤醒第一个goroutine
runtime_Semrelease(&m.sema, true, 1)
}
}
go源码阅读 - sync/mutex的更多相关文章
- go源码阅读 - sync/rwmutex
相比于Mutex来说,RWMutex锁的粒度更细,使用RWMutex可以并发读,但是不能并发读写,或者写写. 1. sync.RWMutex的结构 type RWMutex struct { // 互 ...
- 【原】FMDB源码阅读(一)
[原]FMDB源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 说实话,之前的SDWebImage和AFNetworking这两个组件我还是使用过的,但是对于 ...
- 10 DelayQueue 延时队列类——Live555源码阅读(一)基本组件类
这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 本文由乌合之众 lym瞎编,欢迎转载 www.cnblogs.com/oloroso/ 本文由乌合 ...
- 转-OpenJDK源码阅读导航跟编译
OpenJDK源码阅读导航 OpenJDK源码阅读导航 博客分类: Virtual Machine HotSpot VM Java OpenJDK openjdk 这是链接帖.主体内容都在各链接中. ...
- Netty源码阅读(一) ServerBootstrap启动
Netty源码阅读(一) ServerBootstrap启动 转自我的Github Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速 ...
- JDK1.8源码阅读系列之三:Vector
本篇随笔主要描述的是我阅读 Vector 源码期间的对于 Vector 的一些实现上的个人理解,用于个人备忘,有不对的地方,请指出- 先来看一下 Vector 的继承图: 可以看出,Vector 的直 ...
- boost.asio源码阅读(2) - task_io_service
1.0 task_io_service 在boost.asio源码阅读(1)中,代码已经查看到task_io_service中. 具体的操作调用void task_io_service::init_t ...
- 【 js 基础 】【 源码学习 】backbone 源码阅读(一)
最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...
- 【 js 基础 】【 源码学习 】backbone 源码阅读(三)浅谈 REST 和 CRUD
最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...
随机推荐
- Linux TC 流量控制介绍
前段时间在做一些测试的时候接触到了Linux tc,因为需要对数据包添加延迟,用到了tc中的netem.添加简单的延迟非常简单,像这样一条命令就搞定了:$ tc qdisc add dev eth0 ...
- 手把手带你使用ZigBee——通过爱智控制EFR32,以及 Simplicity Studio 使用过程中注意事项
前言 兄弟们,我发现一个有意思的东西,我在爱智官网翻资料的时候,发现他们终于终于把官网文档的索引优化了!有一说一,真是方便不少,终于不再是一堆文档糊在一坨了. 另外我还发现他们居然做了一个EFR32的 ...
- Java Study two
由于前段时间再做.NET 项目没做更新,今天简单的学习java项目的新建和入门的学习 今日目标 基础语法了解 新建第一个java项目 新建第一个项目Class ->HelloWorld 运行并输 ...
- 搭建分布式事务组件 seata 的Server 端和Client 端详解(小白都能看懂)
一,server 端的存储模式为:Server 端 存 储 模 式 (store-mode) 支 持 三 种 : file: ( 默 认 ) 单 机 模 式 , 全 局 事 务 会 话 信 息 内 存 ...
- 初识$router和$route
初识\(router和\)route 一.前言 vue框架中单页面富应用可以说是其最大的优点功能之一了,应用起来简单直观,说起单页面富应用那就必须得联想到\(router**,但是在项目开发过程中 ...
- error C4996: 'std::_Copy_impl'
以下代码段在VS2008编译可以通过,只是会提示不安全: std::vector<unsigned char> fileData ="asdfsfsfsfsdf";// ...
- 你的图片可能是这样被CORB“拦截”的
问题 最近学习一个uniapp+nodejs的项目,前端写了这样一个标签 <image :src="info.imgUrl" ></image> 按理说不应 ...
- abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?
abstract的method 不可以是static的,因为抽象的方法是要被子类实现的,而static与子类扯不上关系! native方法表示该方法要用另外一种依赖平台的编程语言实现的,不存在着被子类 ...
- Schema和数据类型优化?
整数TinyInt,SmallInt,MediumInt,Int,BigInt 使用的存储8,16,24,32,64位存储空间.使用Unsigned表示不允许负数,可以使正数的上线提高一倍.实数 Fl ...
- spring学习一:spring入门及相关概念介绍
1:Spring的概念:(03年兴起) (1) 开源的轻量级的框架(无需复杂的环境,不依赖其他) (2) 一站式框架(Spring在javaee的三层结构中,对每一层都提供不同的解决技术: ...