img

  • 1 锁概念

  • 1 锁概念

    1.1 锁的分类

    如何理解互斥锁、条件锁、读写锁以及自旋锁?

    5000字 | 24张图带你彻底理解21种并发锁

    undefined

    1.1.1 互斥锁和自旋锁

    互斥锁和自旋锁是最底层的两种锁,其他的很多锁都是基于他们的实现。

    当线程A获取到锁后,线程B再去获取锁,有两种处理方式:

    1. 线程B循环的去尝试获取锁,直到获取成功为止即自旋锁
    2. 线程B放弃获取锁,在锁空闲时,等待被唤醒,即互斥锁

    互斥锁

    互斥锁会释放当前线程的cpu,导致加锁的代码阻塞,直到线程再次被唤醒。互斥锁加锁失败时,会从用户态陷入到内核态,让内核帮我们切换线程,存在一定的性能开销

    while (抢锁(lock) == 没抢到) {睡眠直到锁状态改变再唤醒(lock)}
    
    • 当线程加锁失败,内核会把线程的状态由“运行”设置为“睡眠”,让出cpu
    • 当锁空闲时,内核唤醒线程,状态设置为“就绪”,获取cpu执行

    自旋锁

    自旋锁会自用户态由应用程序完成,不涉及用户态到内核态的转化,没有线程上下文切换,性能相对较好,自旋锁加锁过程:

    while (抢锁(lock) == 没抢到) {}
    
    • 查看锁的状态
    • 锁空闲,获取锁,否则执行(1)

    自旋锁会利用cpu一直工作直到获取到锁,中间不会释放cpu,但如果被锁住的代码执行时间较长,导致cpu空转浪费资源

    1.1.2 读写锁

    读写锁由读锁和写锁组成:

    • 读锁又称为共享锁,S锁
    • 写锁又称为排它锁、X锁,在mysql的事务中大量使用

    写锁是独占锁,一旦线程获取写锁,其他线程不能获取写锁和读锁。

    读锁是共享锁,当线程获取读锁,其他线程可以获取读锁不能获取写锁。因为并发数据读取并不会改变共享数据导致数据不一致。读写锁把对共享资源的读操作和写操作分别加锁控制,能够提高读线程的并发性,适用于读多写少的场景。

    1.1.3 读优先锁、写优先锁、公平读写锁

    读优先锁

    读优先锁希望的是读锁能够被更多的线程获取,可以提高读线程的并发性。线程A获取了读锁,线程B想获取写锁,此时会被阻塞,线程c可以继续获取读锁,直到A和c释放锁,线程B才可以获取写锁。如果有很多线程获取读锁,且加锁的代码执行时间很长,就到导致线程B永远获取不到写锁。

    写优先锁

    写优先锁希望的是写锁能够被优先获取。线程A获取了读锁,线程B想获取写锁,此时会被阻塞,后面获取读锁都会失败,线程A释放锁,线程B可以获取写锁,其他获取读锁的线程阻塞。如果有很多写线程获取写锁,且加锁的代码执行时间很长,就到导致读线程永远获取不到读锁。

    公平读写锁

    上面两种锁都会造成【饥饿】现象,为解决这种问题,可以增加一个队列,把获取锁的线程(不管是写线程还是读线程)按照先进先出的方式排队,每次从队列中取出一个线程获取锁,这种获取锁的方式是公平的。

    1.1.4 乐观锁和悲观锁

    乐观锁

    乐观锁是先修改共享资源,再用历史数据和当前数据比对验证这段时间共享数据有没有被修改,如果没有被修改,那么更新数据,如果有其他线程更新了共享资源,需要重新获取数据,再更新,验证,循环往复的重试,直到更新成功。所以当数据更新操作比较频繁,数据冲突的概率就会比较大,重试的次数就会多,浪费CPU资源。 乐观锁其实全程没有加锁,也叫无锁编程,所以针对读多写少的场景,并发性能较高,典型的实现MVCC,mysql中会使用MVCC构建一致性读来保证可重复读。

    悲观锁

    悲观锁是在访问共享资源之前统统加锁当并发冲突概率较高时,乐观锁不再适用,悲观锁就派上用场。互斥锁、自旋锁都是悲观锁的实现。

    1.2 惊群问题

    [框架]高并发中的惊群效应

    当你往一群鸽子中间扔一块食物,虽然最终只有一个鸽子抢到食物,但所有鸽子都会被惊动来争夺,没有抢到食物的鸽子只好回去继续睡觉, 等待下一块食物到来。这样,每扔一块食物,都会惊动所有的鸽子,即为惊群。

    简单地说:就是扔一块食物,所有鸽子来抢,但最终只一个鸽子抢到了食物。 语义分析:食物只有一块,最终只有一个鸽子抢到,但是惊动了所有鸽子,每个鸽子都跑过来,消耗了每个鸽子的能量。(这个很符合达尔文的进化论,物种之间的竞争,适者生存。)

    操作系统的惊群

    在多进程/多线程等待同一资源时,也会出现惊群。即当某一资源可用时,多个进程/线程会惊醒,竞争资源。这就是操作系统中的惊群。

    1. 惊醒所有进程/线程,导致n-1个进程/线程做了无效的调度,上下文切换,cpu瞬时增高
    2. 多个进程/线程争抢资源,所以涉及到同步问题,需对资源进行加锁保护,加解锁加大系统CPU开销

    1.2.1 惊群的几种情况

    在高并发(多线程/多进程/多连接)中,会产生惊群的情况有:

    1. accept惊群
    2. epoll惊群
    3. nginx惊群
    4. 线程池惊群

    线程池惊群

    在多线程设计中,经常会用到互斥和条件变量的问题。当一个线程解锁并通知其他线程的时候,就会出现惊群的现象。

    pthread_mutex_lock/pthread_mutex_unlock:线程互斥锁的加锁及解锁函数。 pthread_cond_wait:线程池中的消费者线程等待线程条件变量被通知; pthread_cond_signal/pthread_cond_broadcast:生产者线程通知线程池中的某个或一些消费者线程池,接收处理任务; 这里的惊群现象出现在3里,pthread_cond_signal,语义上看,是通知一个线程。调用此函数后,系统会唤醒在相同条件变量上等待的一个或多个线程(可参看手册)。如果通知了多个线程,则发生了惊群。

    正常的用法:

    1. 所有线程共用一个锁,共用一个条件变量
    2. 当pthread_cond_signal通知时,就可能会出现惊群

    解决惊群的方法:

    1. 所有线程共用一个锁,每个线程有自已的条件变量
    2. pthread_cond_signal通知时,定向通知某个线程的条件变量,不会出现惊群

    Copyright © narutohyc.com 2022 all right reserved,powered by Gitbook该文件修订时间: 2022-03-25 15:05:34

    results matching ""

      No results matching ""