Go并发编程-Mutex

Go并发编程-Mutex

简单用

​ 互斥锁Mutex提供了两个方法Lock和Unlock:进入临界区之前调用Lock方法,退出临界区的时候调用Unlock方法,当一个goroutine拿到了锁,就会阻塞其他gotoute在调用lock的方法上,直到这个锁被释放,并自己获取到了锁。

type Count struct {
	sync.Mutex
	Count int
}

func (c *Count) SyncAdd() {
	c.Lock()
	defer c.Unlock()
	c.Count++
}
	1. 使用Mutex是不需要初始化的,在使用时会自动去初始化。所以在使用时不需要自己去创建一个对象
	2. Mutex变量声明时最好声明在其临界变量的上面,然后使用空格把字段分隔开来。逻辑会跟清晰,便于维护
		3. Lock和Unlock方法成对出现,使用defer去解锁。逻辑会跟清晰,便于维护,防止出现漏掉的情况

看实现

type Mutex struct {
	state int32  //标记锁的状态
	sema  uint32 //信号变量用来控制goroutine的阻塞休眠和唤醒
}

​ 使用一个int32类型去标记锁的情况。并使用bit位去表示不同的含义。

const (
	mutexLocked      = 1 << iota // mutex is locked 已锁定
	mutexWoken                   // 唤醒标记
	mutexStarving                //饥饿状态
	mutexWaiterShift = iota      //前三位是状态标记,后面的都是waiter的数量

​ 第1位表示锁定状态,第二位表示唤醒标记,第三位表示饥饿模式,其余用来表示waiter等待的数量。

​ Mutex获取锁的过程经过三次的改版,最新的一版和目前的java中的锁的级别(偏向锁,轻量锁,重量锁)基本相匹配

第一次版本:是一个公平锁,所有的请求锁的goroutine都会在排队等待获取锁,这种锁性能不是很好,涉及到新来的goroutine与队列中的goroutine到底是应该获取锁,如果让新来的gorouine获取锁,不需要切换上下问,性能会比较好。

第二次版本:给新人机会(轻量锁),新来的goroutine与唤醒队列中的goroutine相比,更有机会获取到锁,甚至一个goroutine能连续获取到锁。

第三次版本:多给些机会(偏向锁),新来的goroutine或者是被唤醒的goroutine获取不到锁时,会通过自旋的方式去尝试再次获取锁,如果临界区代码执行非常短,这种是非常好的优化。抢夺锁的goroutine不用通过休眠唤醒方式等待调度,这样性能会比较好。

终极版本:解决饥饿(公平锁),由于新来的goroutine参与竞争锁,大概率是新来的goroutine获取锁,这种导致队列中的goroutine会一直获取不到锁,第四次版本让Mutex变得更加公平。引入了饥饿模式,在处于饥饿模式下,新来的goroutine是不参与竞争锁的,而是直接从队列中唤醒的goroutine去获得锁。正常模式和饥饿模式的转换的时机是队列中的goroutine超过1ms获取不到锁就进入到饥饿模式,否则就退出饥饿模式。

总结:最终版的Mutex与java锁定是非常类似的。在竞争锁时,最开始时是偏向锁级别,通过自旋的方式尝试获取锁,如果没有获取到就晋级为轻量级锁,新来的gorotutine与队列中的goroutine同时竞争,当队列中的goroutine超过1ms没有获取到锁则晋级为重量锁,队列中的goroutine优先获取到锁,不同的是java中不会降级,而Mutex是会降级的,会从饥饿模式退化为普通模式。也就是从重量锁退化为偏向锁

别踩坑

  1. Lock与Unlock不是成对出现

    不成对出现意味着死锁,或者未加锁时Unlock导致panic。死锁情况很好解释,锁一直处于锁定状态,意味着其他的goroutine永远没有机会获取到锁。未加锁的panic是终极版本的Mutex源码中有的,如果没有锁定直接Unlock是会直接panic的

    func (m *Mutex) unlockSlow(new int32) {
    	if (new+mutexLocked)&mutexLocked == 0 {
    		throw("sync: unlock of unlocked mutex")
    	}
      ...
    }
    
    1. Copy已使用的Mutex

    sync的包中的同步原语是不能复制的,因为他们是有状态的对象,复制一个已经加锁的Mutex给一个新的变量明显不符合预期。虽然这种情况很简单,但是还是会很容易出错,因为go的函数调用会自动复制。所以有关锁的参数传递要使用指针的方式。

    1. 重入锁

    java中的ReentrantLock就是重入锁,当一个线程获取到锁时,这个线程是可以无限的再次获取锁的,因为ReentrantLock记录了获取锁的线程的id。但是Mutex不是重入锁,他没有记录Goroutine的id。可以自己手动实现一个重入锁

    package mutex
    
    import (
    	"fmt"
    	"github.com/petermattis/goid"
    	"sync"
    	"sync/atomic"
    )
    // RecursiveMutex 重入锁
    type RecursiveMutex struct {
    	sync.Mutex
    	owner     int64 // 当前持有锁的goroutine id
    	recursion int32 // 重入的次数
    }
    
    func (rm *RecursiveMutex) Lock() {
    	gid := goid.Get()
    
    	if atomic.LoadInt64(&rm.owner) == gid {
    		rm.recursion++
    		return
    	}
    
    	rm.Mutex.Lock()
    
    	atomic.StoreInt64(&rm.owner, gid)
    	rm.recursion = 1
    }
    
    func (rm *RecursiveMutex) Unlock() {
    	gid := goid.Get()
    
    	if atomic.LoadInt64(&rm.owner) != gid {
    		panic(fmt.Sprintf("wrong the owner(%d) %d", rm.owner, gid))
    	}
    
    	rm.recursion--
    	if rm.recursion != 0 {
    		return
    	}
    
    	atomic.StoreInt64(&rm.owner, -1)
    
    	rm.Mutex.Unlock()
    }
    

扩展用

​ Mutex本身只提供了Lock和Unlock的接口,但是在使用过程中会有一些特殊的场景需要特殊使用

  1. Try lock功能

    在Mutex在Luck时会直接进入到阻塞中,但是实际的应用中,当没有获取到锁时,并不希望阻塞住。这个时候就需要一个try Lock的功能,如果上锁成功就返回true,否则返回false

  2. 获取锁的信息

    Mutex一个state字段包含了4层含义,锁的状态,清除标记,饥饿模式,以及waiter的数量,但是并没有获取到这个4个含义的值的具体情况

import (
	"sync"
	"sync/atomic"
	"unsafe"
)

type ExtMutex struct {
	m sync.Mutex
}

const (
	mutexLocked = 1 << iota 
	mutexWoken              
	mutexStarving
	mutexWaiterShift = iota 
)

func (em *ExtMutex) TryLock() bool {
	if atomic.CompareAndSwapInt32((*int32)(unsafe.Pointer(&em.m)), 0, mutexLocked) {
		return true
	}

	old := atomic.LoadInt32((*int32)(unsafe.Pointer(&em.m)))

	if old&(mutexLocked|mutexStarving|mutexWoken) != 0 {
		return false
	}

	new := old | mutexLocked
	return atomic.CompareAndSwapInt32((*int32)(unsafe.Pointer(&em.m)), old, new)
}

func (em *ExtMutex) Count() int32 {
	v := atomic.LoadInt32((*int32)(unsafe.Pointer(&em.m)))
	v = v >> mutexWaiterShift
	v = v + (v & mutexLocked)
	return v
}

func (em *ExtMutex) IsLocked() bool {
	state := atomic.LoadInt32((*int32)(unsafe.Pointer(&em.m)))
	return state&mutexLocked == mutexLocked
}

func (em *ExtMutex) IsWoken() bool {
	state := atomic.LoadInt32((*int32)(unsafe.Pointer(&em.m)))
	return state&mutexWoken == mutexWoken
}

func (em *ExtMutex) IsStarving() bool {
	state := atomic.LoadInt32((*int32)(unsafe.Pointer(&em.m)))
	return state&mutexStarving == mutexStarving
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值