lock是如何实现的,凭啥你lock后我就得等

多线程并发访问共享资源的现象称为Race condition(竞态条件),由于无法确定CPU调度指令的顺序,当出现竞态条件时,就会存在结果的不确定性(就是我们常说的并发安全问题),以及不可重现性的情况(安全问题也是偶发的,往往不容易复现,所以出现问题时,很难排查原因)

举个获取进程ID的例子:(如果你对并发问题如何发生的比较清楚,可以跳过这一段,不影响下面的理解)

当前next_pid=100;

我们拿new_pid=next_pid++;这个操作来说明并发情况下,CPU层面发生了怎样的操作

上面的代码转换为机器指令后如下:

LOAD next_pid Reg1 -->将内存单元next_pid的值加载到Reg1寄存器

STORE Reg1 new_pid -->将Reg1的值存回内存单元new_pid

INC Reg1 -->将Reg1的值加1

STORE Reg1 next_pid -->将Reg1的值存回内存单元next_pid

如果两个线程并发执行上面的语句,在CPU执行指令时,可能出现这种情况:(请忽略图中是进程而不是线程,都一样,一样....)

线程1将100加载到Reg1中,赋值给内存的new_pid后,CPU上下文切换到线程2,此时线程1的堆栈中存储了切换前Reg1的值为100,线程2执行完成后得出的值为101,然后切换到线程1,线程1将Reg1的值恢复成100,继续执行后得出101

 

两个线程并发执行各一次,结果却是101而不是102,这就是并发错误,当然,这仅仅是并发错误的一种情况,实际还有很多种情况会导致错误

为啥出现?

       为何会出现这种情况呢?就是因为线程的执行不是一直连续的,会被操作系统不断切换,单个进程依次执行上面四个指令的过程,很可能被打断,中间插一点其他线程的指令,就影响了最终结果

如何避免呢?自然就是让执行过程不会被打断,拿上面的例子说明就是CPU只会在线程1的4条指令执行完后才会调起其他线程的指令,这种不会被打断的操作称作原子操作(atomic Operation)

如何实现?

       大家都知道,锁可以帮我们实现原子操作,那么锁是如何实现的呢?为啥一个线程调用一个lock()方法就能让其他线程等待呢?锁的实现方式有纯软件层面的,这个实现方式比较复杂,实际场景中应用的也很少

本文只说硬件层面是如何实现的:

在CPU中,有两种原子指令(所谓原子指令就是不会被打断的指令),只要一个CPU支持这两种原子指令中的一种,我们就可以基于此实现锁

第一个是测试和置位,test-and-set,它包含的步骤如下:

1.从内存中读取值

2.测试该值是否为1,并返回true false

3.将内存中的该值设置为1

伪代码如下:

boolean TestAndSet(*target){

boolean result=(*target==1);

*target=1;

return result;

}

第二个是交换,exchange,交换内存中的两个值,伪代码如下:

void Exchange(boolean *a,boolean *b){

boolean temp=*a;

*a=*b;

*b=temp;

}

有了上面两个原子指令的支持,我们就可以实现锁了 

基于test-and-set实现锁

int base=0;//初始状态,0表示没人拿到锁

Lock.acquire(){//竞争锁

while(TestAndSet(base)){}

//只有第一个线程能把base从0变成1,变成1后,其他线程就会一直循环

....拿到锁...

}

Lock.release(){//释放锁

base=0;//release后,之前忙等的线程会跳出while循环,拿到锁

}

忙等的特点是快,一旦在拿到锁的线程释放锁后,立即可以获取响应,其缺点就是耗CPU,可以换成sleep的方式,一旦不满足条件,进入等待队列,等待被已经拿到锁的线程唤醒

基于exchange实现锁:

int base=0;//初始状态,0表示没人拿到锁

Lock.acquire(){//竞争锁

int key=1;

while(key==1){exchange(key,base)}

//只有第一个线程能把base从0变成1,变成1后,其他线程就会一直循环

....拿到锁...

}

Lock.release(){//释放锁

base=0;//release后,之前忙等的线程会跳出while循环,拿到锁

}

好了,你已经知道了,通过硬件级的CPU原子指令,我们可以实现锁操作,解决一些并发问题

只有锁还不够:

       但在实际场景中,光有互斥是不够的,还需要一些同步的机制,甚至在临界区里,我们希望可以有多个线程可以同时执行,在这种情况下,我们只使用锁机制就不够了,需要更高层的一些同步互斥的语义来实现,比如读写锁(多个读操作可以同时进行),比如生产者消费者模型(生产者消费者需要一定的同步机制),下一篇文章,我们继续介绍更高层次的同步互斥语义:信号量和管程

 

PS:如果有不足之处,欢迎指出;如果解决了你的疑惑,就点个赞吧o(* ̄︶ ̄*)o

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值