一文搞懂各种锁-互斥锁-自旋锁-可重入锁-读写锁-悲观锁-乐观锁-分布式锁

原文网址:https://zhuanlan.zhihu.com/p/489305763

一 为什么会有锁机制

1  在多线程情况下共享操作同一个变量时会导致数据不一致出现并发安全问题所以通过锁机制来保证数据的准确和唯一
2  通过锁将可能出现问题的代码用锁对象锁起来,被锁起来的代码就叫同步代码块,同一时间只能有一个线程来访问这个同步代码块

二 什么是临界区

1 每个进程中访问临界资源的那段代码称为临界区criticalsection2 通过锁机制保证每次只允许一个进程进入临界区进入后不允许其他进程进入

三 操作系统的各种锁

3.1 互斥锁

// 互斥锁
互斥锁是一种简单的加锁的方法来控制对共享资源的访问互斥锁只有两种状态,即上锁( lock )和解锁(unlock)如果互斥量已经上锁调用线程会阻塞直到互斥量被解锁在完成了对共享资源的访问后要对互斥量进行解锁


// 互斥锁特点
1. 原子性把一个互斥量锁定为一个原子操作操作系统保证了如果一个线程锁定了一个互斥量没有其他线程在同一时间可以成功锁定这个互斥量2. 唯一性如果一个线程锁定了一个互斥量在它解除锁定之前没有其他线程可以锁定这个互斥量3. 非繁忙等待如果一个线程已经锁定了一个互斥量第二个线程又试图去锁定这个互斥量则第二个线程将被挂起不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止第二个线程则被唤醒并继续执行同时锁定这个互斥量


// 注意
Python,Go,Java都支持互斥锁

3.2 自旋锁

// 自旋锁
自旋锁与互斥量功能一样唯一一点不同的就是互斥量阻塞后休眠让出cpu而自旋锁阻塞后不会让出cpu会一直忙等待直到得到锁原地打转
自旋锁在用户态使用的比较少在内核使用的比较多自旋锁的使用场景锁的持有时间比较短或者说小于2次上下文切换的时间// 自旋锁特点
1 某个协程持有锁时间长等待的协程一直在循环等待消耗CPU资源2 不公平有可能存在有的协程等待时间过程出现线程饥饿这里就是协程饥饿// 注意
Python,Go不支持自旋锁
Java支持自旋锁

Go实现自旋锁

/*
1 锁也是1个变量,初值设为0;
2 1个协程将锁原子性的置为1;
3 操作变量n;
4 操作完成后,将锁原子性的置为0,释放锁。
在1个协程获取锁时,另一个协程一直尝试,直到能够获取锁(不断循环),这就是自旋锁
*/

import (
    "sync/atomic"
    "time"
)
// Spin是一个锁变量,实现了Lock和Unlock方法
type Spin int32
func (l *Spin) Lock() {
    // 原子交换,0换成1
    for !atomic.CompareAndSwapInt32((*int32)(l), 0, 1) {}
}
func (l *Spin) Unlock() {
    // 原子置零
    atomic.StoreInt32((*int32)(l), 0)
}
type Locker interface {
    Lock()
    Unlock()
}
func main() {
    var l Locker
    l = new(Spin)
    var n int
    // 两个协程
    for i := 0; i < 2; i++ {
        go routine(i, &n, l, 200*time.Millisecond)
    }
    select {}
}
func routine(i int, v *int, l Locker, d time.Duration) {
    // 实现自旋加锁
    for {
        func() {
            l.Lock()
            defer l.Unlock()
            *v++
            println(*v, i)
            time.Sleep(d)
        }()
    }
}

3.3 可重入锁(递归锁)

// 可重入锁
为了解决互斥锁导致的死锁问题(哲学家吃面问题)引入可重入锁又叫递归锁
可重入内部维护着一个锁和一个计数器计数器记录了获取锁的次数从而使得资源可以被同一个线程多次获取直到一个线程所有的获取都被释放其他的线程才能获得资源


// 注意
Go不支持可重入锁 //https://blog.csdn.net/qq_39397165/article/details/117433641
Python,Java支持可重入锁

3.4 读写锁

// 读写锁
读写锁允许更改的并行性写的串行性也叫共享互斥锁互斥量要么是锁住状态要么就是不加锁状态而且一次只有一个线程可以对其加锁读写锁可以有3种状态读模式下加锁状态写模式加锁状态不加锁状态一次只有一个线程可以占有写模式的读写锁但是多个线程可以同时占有读模式的读写锁允许多个线程读但只允许一个线程写// 读写锁特点
1 如果有其它线程读数据则允许其它线程执行读操作但不允许写操作2 如果有其它线程写数据则其它线程都不允许读写操作


// 注意
Python不支持读写自行实现https://www.cnblogs.com/LuoboLiam/p/15338632.html
Java,Go支持读写锁

3.5 信号量(Semaphore)

// 信号量
信号量可以理解为多把锁同时允许多个线程来更改数据
信号量是一个计数器可以用来控制多个进程对共享资源的访问
信号量广泛用于进程或线程间的同步和互斥信号量本质上是一个非负的整数计数器它被用来控制对公共资源的访问

// 注意
Go不支持信号量可以自行实现https://studygolang.com/articles/25382?fr=sidebar
Python,Java支持信号量

3.6 条件变量(Condition)

// 条件变量
线程等待只有满足某条件时n个线程才执行
条件变量用来自动阻塞线程直到某特殊情况发生为止条件变量使我们可以睡眠等待某种条件出现条件变量是利用线程间共享的全局变量进行同步的一种机制主要包括两个动作一个线程等待"条件变量的条件成立"而挂起另一个线程使 条件成立”(给出条件成立信号// 注意
Go,Python,Java都支持条件变量

3.7 其他

// 公平锁 / 非公平锁
1 公平锁是指多个线程按照申请锁的顺序来获取锁2 非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序有可能后申请的线程比先申请的线程优先获取锁有可能会造成优先级反转或者饥饿现象

// 可重入锁 / 不可重入锁
1 可重入锁指的是可重复可递归调用的锁在外层使用锁之后在内层仍然可以使用并且不发生死锁这样的锁就叫做可重入锁
2 不可重入锁与可重入锁相反不可递归调用递归调用就发生死锁// 独享锁 / 共享锁
1 独享锁该锁每一次只能被一个线程所持有2 共享锁该锁可被多个线程共有

// 互斥锁 / 读写锁
1 互斥锁:在访问共享资源之前对进行加锁操作在访问完成之后进行解锁操作加锁后任何其他试图再次加锁的线程会被阻塞直到当前进程解锁
2 读写锁既是互斥锁又是共享锁read模式是共享write是互斥(排它锁)// 分段锁
分段锁: 其实是一种锁的设计并不是具体的一种锁
容器里有多把锁每一把锁用于锁容器其中一部分数据那么当多线程访问容器里不同数据段的数据时线程间就不会存在锁竞争

// 偏向锁 / 轻量级锁 / 重量级锁
1 偏向锁:是指一段同步代码一直被一个线程所访问那么该线程会自动获取锁降低获取锁的代价轻量级
2 轻量级锁:是指当锁是偏向锁的时候被另一个线程所访问偏向锁就会升级为轻量级锁其他线程会通过自旋的形式尝试获取锁不会阻塞提高性能重量级锁
3 重量级锁: 是指当锁为轻量级锁的时候另一个线程虽然是自旋但自旋不会一直持续下去当自旋一定次数的时候还没有获取到锁就会进入阻塞该锁膨胀为重量级锁重量级锁会让其他申请的线程进入阻塞性能降低

四 乐观锁/悲观锁

// 悲观锁

总是假设最坏的情况每次去拿数据的时候都认为别人会修改所以每次在拿数据的时候都会上锁这样别人想拿这个数据就会阻塞直到它拿到锁共享资源每次只给一个线程使用其它线程阻塞用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制比如行锁表锁等读锁写锁等都是在做操作之前先上锁

//乐观锁
总是假设最好的情况每次去拿数据的时候都认为别人不会修改所以不会上锁但是在更新的时候会判断一下在此期间别人有没有去更新这个数据可以使用版本号机制和CAS算法实现乐观锁适用于多读的应用类型这样可以提高吞吐量像数据库提供的类似于write_condition机制其实都是提供的乐观锁

五 分布式锁

在分布式系统中访问共享资源就需要一种互斥机制来防止彼此之间的互相干扰以保证一致性在这种情况下就需要用到分布式锁

为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行在传统单体应用单机部署的情况下可以使用并发处理相关的功能进行互斥控制但是随着业务发展的需要原单体单机部署的系统被演化成分布式集群系统后由于分布式系统多线程多进程并且分布在不同机器上这将使原单机部署情况下的并发控制锁策略失效单纯的应用并不能提供分布式锁的能力为了解决这个问题就需要一种跨机器的互斥机制来控制共享资源的访问这就是分布式锁

// 分布式锁的多种实现方式
https://www.cnblogs.com/liuqingzheng/p/11080501.html
posted @ 2022-12-08 21:48  MaxBruce  阅读(415)  评论(0)    收藏  举报