42

原子操作&普通锁&读写锁

 5 years ago
source link: http://www.cnblogs.com/hlxs/p/10281017.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一:原子操作CAS(compare-and-swap)

原子操作分三步:读取addr的值,和old进行比较,如果相等,则将new赋值给*addr,他能保证这三步一起执行完成,叫原子操作也就是说它不能再分了,当有一个CPU在访问这块内容addr时,其他CPU就不能访问

func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
TEXT ·CompareAndSwapUint64(SB),NOSPLIT,$0-25
   MOVD   addr+0(FP), R3
   MOVD   old+8(FP), R4
   MOVD   new+16(FP), R5
   SYNC
   LDAR   (R3), R6
   CMP    R6, R4
   BNE    7(PC)
   STDCCC R5, (R3)
   BNE    -4(PC)
   ISYNC
   MOVD   $1, R3
   MOVB   R3, swapped+24(FP)
   RET
   MOVB   R0, swapped+24(FP)
    RET
 

二:普通锁

加锁(Mutex.Lock)

1:原子操作加锁:原子操作判断是否已经被加锁,如果没有加锁,原子操作加锁,直接返回,很快吗!

2:执行旋转锁:已经被加锁,判断是否可以执行旋转锁,执行旋转锁,原子判断是否可以加锁,若可以,加锁返回

3:当前G休眠等待被唤醒:在执行旋转锁期间,锁还是没释放,那就只能让当前协程休眠,等待被唤醒,当锁被释放后,当前G被唤醒继续执行

释放锁(Mutex.UnLock)

1:将加锁状态去掉,判断是否有等待的协程,如没有直接返回

2:若有等待协程,将状态设置成唤醒状态

3:唤醒一个等待协程

三:读写锁

读写锁基于普通锁实现

加写锁(RWMutex.Lock)

1:加普通锁

2:改读锁的数量readerCount -= 1 << 30

3:如果有正在读的锁,等待直到读锁完成,读写不能同时进行

释放写锁(RWMutex.UnLock)

1:改读锁的数量readerCount += 1 << 30,加锁的时候减了这么多,释放锁的时候加回来

2:如果readerCount>=  1 << 30,抛异常,释放没有加锁的锁

3:唤醒所有正在等待读的协程

4:释放普通锁

加读锁(RWMutex.RLock)

1:原子操作读锁数量加1,readerCount+=1

2:如果rederCount<0,说明有写功能正在执行,协程进入睡眠状态,等待写完之后被唤醒

3:如果没有正在执行的写锁,就完事了,整个加锁操作就只执行了一个原子操作,还是很快的

释放读锁(RWMutex.RUnLock)

1:原子操作读锁数量减1

2:如果读锁数量==-1,或==-1 << 30,说明释放了一个没有加读锁的锁,或者释放了一个正在写的锁,直接报错

3:如果有正在等待的写锁,唤醒它,否则整个释放读锁也就执行了一个原子操作

所以说,锁是基于原子操作的,原子操作保证了数据的一致性,读写锁基于普通锁来实现,对于一个写少读多的程序来说,读写锁会比普通锁快很多

加锁原理

1:先是CAS的方式尝试获取锁,如果获取到了,就锁住,并继续执行被锁住的代码,然后在释放锁

2:CAS没有拿到锁,就只能等待了,比如有10个协程(G)在等这个待锁,go并不是一把锁创建一个队列,而是默认创建251个队列,通过hash的方式将G加入队列,确保等待同一把锁的G在同一个队列,然后将当前G执行上下文信息保存到G.sched,下次就可以继续从这里执行,这样这个等待的G就这样被扔到队列中了,而不是将这个G状态改成等待状态等待被唤醒,G去睡觉了,P还得继续执行,于是会找一个P,继续执行

解锁原理

1:通过锁定位到对应的队列,所有等待这把锁的G都在这个队列中,查找是否有等待的G,没有就返回

2:有就将G状态改成可运行,并加入到运行队列,等待被调度

关于G调度请看我的这篇文章:go并发调度原理学习


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK