一文看懂临界区、互斥锁、同步锁、临界区、信号量、自旋锁等名词!

关于线程安全的专有名词有一大堆。你们突然之间问我这个名词是什么意思,那个名词是什么意思我还真不一定能给你准确的回答。这还别说一门语言一堆名词。其实有些名词叫法不同,实际上就是一个意思。

A 语言有这个名词,B 语言就起另外一个名词。不能大胆的雷同,所以就改变一个叫法,其本质还是一样的。

为了减少大家在私信我,那我今天就来扯一扯,竞态条件,竞态资源,轮询忙等,锁变量,原子性,TSL,阻塞,睡眠,唤醒,管程,互斥锁,同步锁,临界区,互斥量,信号量,自旋锁等各个专业名词的实际所代表的含义。

竞态条件:多线程的核心矛盾是“竞态条件”,即多个线程同时读写某个字段。

竞态资源:竞态条件下多线程争抢的是“竞态资源”。

临界区:涉及读写竟态资源的代码片段叫“临界区”。

互斥:保证竟态资源安全的最朴素的一个思路就是让临界区代码“互斥”,即同一时刻最多只能有一个线程进入临界区。

最朴素的互斥手段:在进入临界区之前,用if检查一个bool值,条件不满足就“忙等”。这叫“锁变量”。但锁变量不是线程安全的。因为“检查-占锁”这个动作不具备“原子性”。

TSL:“TSL指令”就是原子性地完成“检查-占锁”的动作。

自旋锁:就算不用TSL指令,也可以设计出线程安全的代码,有一种既巧妙又简洁的结构叫“自旋锁”。当然还有其他更复杂的锁比如“Peterson锁”。

忙等待:但自旋锁的缺点是条件不满足时会“忙等待”,需要后台调度器重新分配时间片,效率低。

解决忙等待问题的是:“sleep”和“wakeup”两个原语。sleep阻塞当前线程的同时不会让出它占用的锁。wakeup可以唤醒在目标锁上睡眠的线程。

互斥量:使用sleep和wakeup原语,保证同一时刻只有一个线程进入临界区代码片段的锁叫“互斥量”。

信号量:把互斥锁推广到"N"的空间,同时允许有N个线程进入临界区的锁叫“信号量”。互斥量和信号量的实现都依赖TSL指令保证“检查-占锁”动作的原子性。

管程:把互斥量交给程序员使用太危险,有些编程语言实现了“管程”的特性,从编译器的层面保证了临界区的互斥,比如Java的synchronized关键字。

互斥锁、独占锁、内置锁:并没有“同步锁”这个名词,Java的synchronized正确的叫法应该是“互斥锁”,“独占锁”或者“内置锁”。但有的人“顾名思义”叫它同步锁。

下面我们简单的来扩展一下。比如下面的程序:

多线程同时执行这段代码可能就会出错。当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。上例中 add() 方法就是一个临界区,它会产生竞态条件。在临界区中使用适当的同步就可以避免竞态条件。

上面代码中 occupied 就是锁变量。

自旋锁的关键就是用一个 while 轮询,代替 if 检查状态,这样就算线程切出去,另一个线程也因为条件不满足循环忙等,不会进入临界区。这是一个非常常用的结构,不光用在自旋锁,基本是使用条件变量 wait(),notifyAll() 时候的一种惯用法。

自旋锁的缺点是循环忙等。如果并发的线程不像进程调度那样在时间片用完以后会自动切换上下文,就会形成死锁。所以最好在条件不满足的时候,让出线程的控制权,让其他线程有机会执行来使条件满足。

线程安全不管搞出多少名词,都逃不出 3 大核心:原子性、可见性、有序性。推荐阅读《Java 线程安全的3大核心:原子性、可见性、有序性》,其他的相关锁知识,请查看我公众号里的相关文章。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券