返回顶部
首页 > 资讯 > 精选 >Go语言中锁如何实现
  • 797
分享到

Go语言中锁如何实现

2023-07-05 12:07:16 797人浏览 泡泡鱼
摘要

今天小编给大家分享一下Go语言中锁如何实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。Lock// Lock&n

今天小编给大家分享一下Go语言中如何实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

Lock

// 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  }  // Slow path (outlined so that the fast path can be inlined)  //已经锁上的写成进入慢锁流程  m.lockSlow()}

lockSlow

func (m *Mutex) lockSlow() {  var waitStartTime int64 //执行时间  starving := false //当前请求是否是饥饿模式  awoke := false //当前请求是否是唤醒状态  iter := 0 //自旋次数  old := m.state //旧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.    //旧state值已上锁,并且未进入饥饿模式,且可以自旋,进入自旋逻辑    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.      // 当前协程未唤醒       //&& old.state 为未唤起状态,就是说没有其他被唤起的waiter      //&& waiter数>0       //&& m.state标记为唤起状态成功      if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&        atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {        //标记当前协程为唤起状态        //r: 这是为了通知在解锁Unlock()中不要再唤醒其他的waiter了        awoke = true      }      //自旋      runtime_doSpin()      //自旋计数器      iter++      old = m.state      continue    }    //r: old是锁当前的状态,new是期望的状态,以期于在后面的CAS操作中更改锁的状态    //new代表期望的state值    new := old    // Don't try to acquire starving mutex, new arriving goroutines must queue.    //old不是饥饿状态,new带上上锁标志位,也就是饥饿状态不上锁    if old&mutexStarving == 0 {      new |= mutexLocked    }    //旧state值是上锁状态或饥饿状态,新state waiter数+1    //r: 表示当前goroutine将被作为waiter置于等待队列队尾    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.    //当前协程为饥饿状态&&旧state已上锁,新state加饥饿标志位    if starving && old&mutexLocked != 0 {      new |= mutexStarving    }    //r:? 当awoke为true,则表明当前goroutine在自旋逻辑中,成功修改锁的Woken状态位为1    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")      }      //新state关闭唤醒标志位      //r: 因为在后续的逻辑中,当前goroutine要么是拿到锁了,要么是被挂起。      // 如果是挂起状态,那就需要等待其他释放锁的goroutine来唤醒。      // 假如其他goroutine在unlock的时候发现Woken的位置不是0,则就不会去唤醒,那该goroutine就无法再醒来加锁。(见unlock逻辑)      new &^= mutexWoken    }    //r: 尝试将锁的状态更新为期望状态    if atomic.CompareAndSwapInt32(&m.state, old, new) {      //旧state不是锁或饥饿状态,上锁成功,返回      if old&(mutexLocked|mutexStarving) == 0 {        break // locked the mutex with CAS      }      // If we were already waiting before, queue at the front of the queue.      //r: 如果走到这里,那就证明当前goroutine没有获取到锁      // 这里判断waitStartTime != 0就证明当前goroutine之前已经等待过了,则需要将其放置在等待队列队头      //进入队列是否排在最前      queueLifo := waitStartTime != 0      if waitStartTime == 0 {        waitStartTime = runtime_nanotime()      }      //阻塞      runtime_SeMacquireMutex(&m.sema, queueLifo, 1)      //r: 被信号量唤醒之后检查当前goroutine是否应该表示为饥饿      // (这里表示为饥饿之后,会在下一轮循环中尝试将锁的状态更改为饥饿模式)      // 1. 如果当前goroutine已经饥饿(在上一次循环中更改了starving为true)      // 2. 如果当前goroutine已经等待了1ms以上            //被信号量唤醒后当前协程是否进入饥饿状态      //1. 之前是饥饿状态      //2. 运行时间超过1ms      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.        //饥饿模式协程是在Unlock()时handoff到当前协程的                //r:? 如果当前锁既不是被获取也不是被唤醒状态,或者等待队列为空        // 这代表锁状态产生了不一致的问题        if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {          throw("sync: inconsistent mutex state")        }        //m.state 上锁,waiter数-1        delta := int32(mutexLocked - 1<<mutexWaiterShift)        //当前协程不是饥饿状态或旧state的waiter数=1,则m.state饥饿标志位置0        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 = true      iter = 0    } else {      //执行循环前的语句,恢复最新现场      old = m.state    }  }  if race.Enabled {    race.Acquire(unsafe.Pointer(m))  }}

Unlock

// 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取消锁状态,返回值new代表修改后的新值  //如果为0代表没有其他锁了,退出;否则进入unlockSlow()  //锁空闲有两种情况:  //1. 所有位为0,代表没有锁了  //2. 标志位为0, waiter数量>0,还有协程在等待解锁  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)  }}

UnlockSlow

func (m *Mutex) unlockSlow(new int32) {  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.      //解锁,结束,退出      //1. 没有waiter了      //2. 已上锁      //3. 锁处于唤醒状态,表示有协程被唤醒      //4. 饥饿模式, 所有权交给了被解锁饥饿模式的waiter      if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {        return      }      // Grab the right to wake someone.      // 如果能走到这,那就是上面的if判断没通过      // 说明当前锁是空闲状态,但是等待队列中有waiter,且没有goroutine被唤醒      // 所以,这里我们想要把锁的状态设置为被唤醒,等待队列waiter数-1      new = (old - 1<<mutexWaiterShift) | mutexWoken      if atomic.CompareAndSwapInt32(&m.state, old, new) {        //通过信号量唤醒某一个waiter,退出        runtime_Semrelease(&m.sema, false, 1)        return      }      //失败的话,更新old信息,进入下个循环      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.    //饥饿模式,唤醒等待队列队头waiter    runtime_Semrelease(&m.sema, true, 1)  }}

其他关键函数

runtime_canSpin

是否可自旋,不展开

runtime_doSpin

核心是汇编实现,循环执行三十次PAUSE指令

runtime_SemacquireMutex

信号量上锁

sem来自单词semaphore 信号量

runtime_Semrelease

信号量释放

func runtime_Semrelease(s *uint32, handoff bool, skipframes int)
If handoff is true, pass count directly to the first waiter.

handoff 就是传球的意思,handoff 为 false 时,仅仅唤醒等待队列中第一个协程,但是不会立马调度该协程;当 handoff 为 true 时,会立马调度被唤醒的协程,此外,当 handoff = true 时,被唤醒的协程会继承当前协程的时间片。具体例子,假设每个 goroutine 的时间片为 2ms,gorounte A 已经执行了 1ms,假设它通过 runtime_Semrelease(handoff = true) 唤醒了 goroutine B,则 goroutine B 剩余的时间片为 2 - 1 = 1ms。

golang 中 sync.Mutex 的实现

semrelease1(addr, handoff, skipframes) 参数handoff若为true,则让被唤醒的g立刻继承当前g的时间片继续执行。若handoff为false,则把刚被唤醒的g放到当前p的runq中。

Golang sync.Mutex 源码分析

RWMutex

很简单,看源码就行

[Go并发] - RWMutex源码解析

type RWMutex struct {  w           Mutex  // held if there are pending writers  writerSem   uint32 // semaphore for writers to wait for completing readers  readerSem   uint32 // semaphore for readers to wait for completing writers  readerCount int32  // number of pending readers 当前读锁数量  readerWait  int32  // number of departing readers 要离开的读锁数量,暨等待写锁解锁,解锁后可以释放的读锁数量}

Lock()

// Lock locks rw for writing.// If the lock is already locked for reading or writing,// Lock blocks until the lock is available.func (rw *RWMutex) Lock() {  if race.Enabled {    _ = rw.w.state    race.Disable()  }  // First, resolve competition with other writers.    rw.w.Lock() //通过sync.Lock()限制多写锁进入下边的逻辑  // Announce to readers there is a pending writer.  //r值不变, rwmutexMaxReaders值为1<<30  //可以理解为只要读锁的数量小于1<<30位,rw.readerCount值<0表示有写锁.  //也可以理解为加上一个负数,将31位以上都标记为1,代表有写锁, 剩余30位记录读锁数量  r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders  // Wait for active readers.  //r!=0 有读锁,不能释放写锁  //将readerCount转移到readerWait,readerWait的新值!=0 (以上可以翻译为有读锁,将读锁数转移到读等待数,然后写锁阻塞,)  // 满足上面两个条件,写锁阻塞, 等待唤醒,不返回  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))  }}

UnLock()

// Unlock unlocks rw for writing. It is a run-time error if rw is// not locked for writing on entry to Unlock.//// As with Mutexes, a locked RWMutex is not associated with a particular// goroutine. One goroutine may RLock (Lock) a RWMutex and then// arrange for another goroutine to RUnlock (Unlock) it.func (rw *RWMutex) Unlock() {  if race.Enabled {    _ = rw.w.state    race.Release(unsafe.Pointer(&rw.readerSem))    race.Disable()  }  // Announce to readers there is no active writer.\  //将Lock()方法减去的值加回来,变成正数  r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)  if r >= rwmutexMaxReaders {    race.Enable()    throw("sync: Unlock of unlocked RWMutex")  }  // Unblock blocked readers, if any.  //唤醒在RLock()方法阻塞的读操作,数量为r  for i := 0; i < int(r); i++ {    runtime_Semrelease(&rw.readerSem, false, 0)  }  // Allow other writers to proceed.  rw.w.Unlock()  if race.Enabled {    race.Enable()  }}

RLock()

// RLock locks rw for reading.//// It should not be used for recursive read locking; a blocked Lock// call excludes new readers from acquiring the lock. See the// documentation on the RWMutex type.func (rw *RWMutex) RLock() {  if race.Enabled {    _ = rw.w.state    race.Disable()  }  //<0表示已上写锁,阻塞  if atomic.AddInt32(&rw.readerCount, 1) < 0 {    // A writer is pending, wait for it.    runtime_SemacquireMutex(&rw.readerSem, false, 0)  }  if race.Enabled {    race.Enable()    race.Acquire(unsafe.Pointer(&rw.readerSem))  }}

UnRLock()

// RUnlock undoes a single RLock call;// it does not affect other simultaneous readers.// It is a run-time error if rw is not locked for reading// on entry to RUnlock.func (rw *RWMutex) RUnlock() {  if race.Enabled {    _ = rw.w.state    race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))    race.Disable()  }   //<0表示已上写锁,慢解锁  if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {    // Outlined slow-path to allow the fast-path to be inlined    rw.rUnlockSlow(r)  }  if race.Enabled {    race.Enable()  }}// RUnlock undoes a single RLock call;// it does not affect other simultaneous readers.// It is a run-time error if rw is not locked for reading// on entry to RUnlock.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)  }}

以上就是“Go语言中锁如何实现”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注编程网精选频道。

--结束END--

本文标题: Go语言中锁如何实现

本文链接: https://lsjlt.com/news/351681.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Go语言中锁如何实现
    今天小编给大家分享一下Go语言中锁如何实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。Lock// Lock&n...
    99+
    2023-07-05
  • 如何在Go语言中使用锁实现线程安全
    在Go语言中使用锁实现线程安全 随着并发编程的不断普及,保证数据在多个goroutine之间安全访问变得尤为重要。在Go语言中,可以使用锁来实现线程安全,确保共享资源在并发环境下的访问...
    99+
    2024-04-02
  • Go语言实现分布式锁
    目录1. go实现分布式锁1.1 redis_lock.go1.2 retry.go1.3 lock.lua1.4 lua_unlock.lua1.5 refresh.lua1.6 ...
    99+
    2023-01-14
    Go分布式锁 Go锁原理
  • 如何解读Go语言锁
    这篇文章将为大家详细讲解有关如何解读Go语言锁,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。var l sync.Mutexvar a stringfunc...
    99+
    2024-04-02
  • 怎么在Go语言中实现锁机制
    本文小编为大家详细介绍“怎么在Go语言中实现锁机制”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么在Go语言中实现锁机制”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。Go语言的锁在Go语言中,最常用的锁是互斥...
    99+
    2023-07-06
  • Go语言中如何实现并发?
    Go语言作为一门新兴的编程语言,其最大的特点之一就是并发编程,它可以轻松地实现高并发的任务。那么,Go语言中如何实现并发呢?本文将为您详细解答。 goroutine goroutine是Go语言中的轻量级线程,它可以在一个单独的线程中...
    99+
    2023-09-30
    并发 shell bash
  • 在Go语言中如何实现转码
    近年来,随着互联网的蓬勃发展,各种编程语言也呈现出繁荣的态势。其中,Go语言因为其高性能、易于上手等特点,备受广大程序员的青睐。而在Go语言的应用中,转码这个功能也是十分常见的。今天,就让我们一同深入探讨一下,在Go语言中如何实现转码。一、...
    99+
    2023-05-14
  • 一文带你了解Go语言中锁的实现
    目录前言MutexLocklockSlowUnlockUnlockSlow其他关键函数RWMutexLock()UnLock()RLock()UnRLock()前言 此文为学习go锁...
    99+
    2023-03-15
    Go语言 锁 Go语言 读写锁 Go 锁
  • Go语言如何实现多态 
    这篇“Go语言如何实现多态 ”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Go语言如何实现多态 ”文章吧...
    99+
    2023-06-30
  • 如何用 Go 语言实现 SCP
    随着数据传输的不断增长,传输大量数据时如何确保数据安全和传输效率变得越来越重要。SCP (Secure Copy Protocol)是一种安全传输文件的协议,与SSH (Secure Shell)一起使用。本文将介绍如何用 Go 语言实现 ...
    99+
    2023-05-14
  • 如何解决Go语言中的死锁问题?
    如何解决Go语言中的死锁问题?Go语言具有并发编程的特性,可以通过使用goroutine和channel来实现并发操作。然而,在并发编程中,死锁是一个常见的问题。当goroutine之间相互依赖于彼此的资源,并且在访问这些资源时产生了循环依...
    99+
    2023-10-22
    解决方案 Go语言 死锁
  • 如何利用Redis和Go语言实现分布式锁功能
    如何利用Redis和Go语言实现分布式锁功能引言:在分布式系统中,为了保证数据的一致性和并发安全,经常需要使用分布式锁来实现资源的互斥访问。本文将介绍如何利用Redis和Go语言实现分布式锁功能,并提供具体的代码示例。一、什么是分布式锁分布...
    99+
    2023-10-22
    Go语言 redis 分布式锁
  • Go 语言中,如何实现同步缓存?
    在 Go 语言中,实现同步缓存是一项非常常见的任务。在这篇文章中,我们将介绍如何使用 Go 语言的 sync 包实现同步缓存。 缓存是一个用于存储临时数据的区域,可以让应用程序更快地访问和操作数据。在许多应用程序中,缓存被广泛使用,以提高性...
    99+
    2023-06-28
    关键字 同步 缓存
  • GO 语言中的自然语言处理技术如何实现?
    自然语言处理(Natural Language Processing, NLP)是计算机科学与人工智能领域的重要分支,其目的是让计算机能够理解和处理人类语言。在现代社会中,自然语言处理已经广泛应用于搜索引擎、机器翻译、智能客服、智能音箱等...
    99+
    2023-09-05
    自然语言处理 shell 对象
  • Go语言状态机如何实现
    这篇文章主要介绍“Go语言状态机如何实现”,在日常操作中,相信很多人在Go语言状态机如何实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Go语言状态机如何实现”的疑惑有所帮助!接下来,请跟着小编一起来学习吧...
    99+
    2023-07-05
  • go语言如何实现全排列
    今天小编给大家分享一下go语言如何实现全排列的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。思路:首先画出全排列的树形结构,以...
    99+
    2023-07-05
  • 如何在Go语言中实现JavaScript中的并发?
    JavaScript是一种广泛使用的编程语言,它具有良好的并发处理能力。但是,Go语言同样是一种非常强大的编程语言,它也具有非常出色的并发处理能力。在本文中,我们将介绍如何在Go语言中实现JavaScript中的并发。 一、JavaScr...
    99+
    2023-06-23
    并发 javascript 面试
  • 如何在Go语言中实现路由中间件
    如何在Go语言中实现路由中间件,需要具体代码示例引言:在Go语言的Web开发中,路由是不可或缺的一部分。路由中间件是一种在请求到达目标处理函数之前执行的功能模块。它们可以对请求进行拦截、验证、记录等操作,从而帮助开发者处理一些通用功能,并提...
    99+
    2023-12-17
    路由 实现 中间件
  • 如何在Go语言中实现并发编程?
    Go语言是一种现代化的编程语言,它的并发编程特性使其成为开发高性能网络应用和分布式系统的理想选择。本文将介绍Go语言中的并发编程,包括协程、通道和锁等重要概念,以及如何使用它们来实现高效的并发编程。 协程 协程是Go语言中并发编程的核心概...
    99+
    2023-06-21
    并发 ide npm
  • 如何在go语言项目中实现并发
    这期内容当中小编将会给大家带来有关如何在go语言项目中实现并发,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。1、启动go语言的协程package main import (&...
    99+
    2023-06-08
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作