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 ...
随机推荐
- rest-framework之视图和源码解析
视图和源码解析 通过使用mixin类编写视图: from rest_framework import mixins from rest_framework import generics class ...
- 使用cgroup和tc限制带宽
cgroup子系统net_cls 可以给 packet 打上 classid 的标签,用于过滤分类,这个classid就是用于标记skb所属的 qdisc class 的.有了这个标签,流量控制器(t ...
- Drools 规则引擎应用 看这一篇就够了
1 .场景 1.1需求 商城系统消费赠送积分 100元以下, 不加分 100元-500元 加100分 500元-1000元 加500分 1000元 以上 加1000分 ...... 1.2传统做法 1 ...
- idea使用技巧、心得1
0.安装idea之后的准备 (1) 永久快乐使用:在我的博客搜索安装idea关键词既可 (2) 取消更新: (3) idea 官网的关于idea的使用手册:https://www.jetbrains. ...
- SpringAOP--代理
前言 在Spring或者SpringBoot中,可以通过@Aspect注解和切点表达式等配置切面,实现对某一功能的织入.然而其内部到底是如何实现的呢? 实际上,Spring在启动时为切点方法所在类生成 ...
- 什么是ORM思想?常用的基于ORM的框架有哪些?各有什么特点?
ORM的全称是Object-Relational Mapping,即对象关系映射.ORM思想的提出来源于对象与关系之间相悖的特性.我们很难通过对象的继承与聚合关系来描述数据表中一对一.一对多以及多对多 ...
- 什么是 rabbitmq?
采用 AMQP 高级消息队列协议的一种消息队列技术,最大的特点就是消费并不需要确保提供方存在,实现了服务之间的高度解耦
- 什么是 OAuth?
OAuth 代表开放授权协议.这允许通过在 HTTP 服务上启用客户端应用程序(例 如第三方提供商 Facebook,GitHub 等)来访问资源所有者的资源.因此,您可 以在不使用其凭据的情况下与另 ...
- 转:C++初始化成员列表
转载至:https://blog.csdn.net/zlintokyo/article/details/6524185 C++初始化成员列表和新机制初始化表达式列表有几种用法: 1.如果类存在继承关系 ...
- Zookeeper 的典型应用场景 ?
Zookeeper 是一个典型的发布/订阅模式的分布式数据管理与协调框架,开发人员 可以使用它来进行分布式数据的发布和订阅. 通过对 Zookeeper 中丰富的数据节点进行交叉使用,配合 Watch ...