系统性能调优必知必会
陶辉
智链达 CTO,前阿里云 P8 高级技术专家
36367 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 47 讲
系统性能调优必知必会
15
15
1.0x
00:00/00:00
登录|注册

06 | 锁:如何根据业务场景选择合适的锁?

通过CPU提供的CAS操作完成验证
适用于并发冲突概率很低的情况
无锁编程
读优先锁、写优先锁、公平读写锁
读锁允许并发持有,写锁是独占锁
读锁和写锁
适用于能够明确区分出读和写两种场景的情况
忙等待,与CPU紧密配合,减少循环等待时的耗电量
适用于被锁住的代码执行时间很短的情况
通过CPU提供的CAS函数在用户态完成加锁与解锁操作
通过内核执行线程切换及时释放资源
适用于被锁住的代码执行时间不可控的情况
加锁成本高
乐观锁
读写锁
自旋锁
互斥锁
高并发下同步资源选择合适的锁

该思维导图由 AI 生成,仅供参考

你好,我是陶辉。
上一讲我们谈到了实现高并发的不同方案,这一讲我们来谈谈如何根据业务场景选择合适的锁。
我们知道,多线程下为了确保数据不会出错,必须加锁后才能访问共享资源。我们最常用的是互斥锁,然而,还有很多种不同的锁,比如自旋锁、读写锁等等,它们分别适用于不同的场景。
比如高并发场景下,要求每个函数的执行时间必须都足够得短,这样所有请求才能及时得到响应,如果你选择了错误的锁,数万请求同时争抢下,很容易导致大量请求长期取不到锁而处理超时,系统吞吐量始终维持在很低的水平,用户体验非常差,最终“高并发”成了一句空谈。
怎样选择最合适的锁呢?首先我们必须清楚加锁的成本究竟有多大,其次我们要分析业务场景中访问共享资源的方式,最后则要预估并发访问时发生锁冲突的概率。这样,我们才能选对锁,同时实现高并发和高吞吐量这两个目标。
今天,我们就针对不同的应用场景,了解下锁的选择和使用,从而减少锁对高并发性能的影响。

互斥锁与自旋锁:休眠还是“忙等待”?

我们常见的各种锁是有层级的,最底层的两种锁就是互斥锁和自旋锁,其他锁都是基于它们实现的。互斥锁的加锁成本更高,但它在加锁失败时会释放 CPU 给其他线程;自旋锁则刚好相反。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入介绍了在多线程编程中如何选择合适的锁来优化高并发性能。首先对互斥锁和自旋锁进行了比较,指出了它们适用的不同场景。接着详细解释了自旋锁的原理和“忙等待”机制,以及读写锁的性能提升。文章还介绍了乐观锁的概念和应用场景,强调了在冲突概率较低时可以选择无锁编程。总结时强调了选择合适的锁能够大幅提升高并发服务的性能。整体而言,本文通过对不同类型锁的特点和适用场景进行分析,帮助读者了解如何选择合适的锁来提高高并发性能。文章内容丰富,涵盖了多种锁的原理和应用,对于需要深入了解多线程编程的读者具有很高的参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《系统性能调优必知必会》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(28)

  • 最新
  • 精选
  • book尾汁
    总结下,我的理解 不对的欢迎指出: 四种IO模型 同步阻塞: 调用了阻塞的系统调用,内核会将线程置于休眠状态,并进行调度 同步非阻塞: 调用了非阻塞的系统调用,系统调用会立刻返回结果.通过重试的方式来不断获取结果,直到满足条件 异步阻塞: 会通过注册回调函数来实现结果的通知,注册完成线程会被内核挂起进入休眠状态 异步非阻塞: 同上,但注册完成后线程可以继续执行. 同步: 等待结果的返回才能进行下一步操作 异步:不一直等待结果的返回,而是通过向IO调度框架注册回调函数的方式来进行通知.通过框架来实现 阻塞: 不会立刻返回结果,此时线程会被内核挂起,让出cpu 非阻塞: 立刻返回结果 异步非阻塞放在一起用才能起到并发的作用. 同步非阻塞也可以实现并发. 同步与异步是针对编程方式的 阻塞与非阻塞的因为系统调用的实现方式导致的 协程: 将异步的注册回调函数以及非阻塞的系统调用来封装成一个阻塞的协程,即将等待回调时通过线程中的上下文切换来实现线程的无感知切换,感觉有点类似于异步阻塞,但阻塞是用户态的,也就是是由用户态来进行线程的虚拟的休眠(通过线程上下文的切换) 锁 两种基本锁 自旋锁 : 通过CAS函数来实现,将观察锁的状态与获取锁合并为一个硬件级的指令,通过在用户态来观察锁的状态,并进行获取锁,来避免因获取锁失败导致的线程休眠.获取锁失败会忙等待,即过一段时间在去获取锁(通过循环实现等待时间),通过pause指令来减少循环等待时的耗电量. 互斥锁: 一种独占锁,获取失败的线程会被内核置为休眠状态. 其他锁都是通过这两种锁实现的 读写锁 读优先锁 写优先锁 乐观锁:乐观锁并没有加锁,而是通过执行完成后的版本号对比来实现

    作者回复: 完全正确!

    2020-05-12
    7
    24
  • 那时刻
    Go里的自旋锁需要自己实现,方便协程调度。协程使用自旋锁的时候,这是spinLock 的Lock方法 for !atomic.CompareAndSwapUint32(sl, 0, 1) { runtime.Gosched() } 其中runtime.Gosched,是把阻塞的协程调度出去,这样调度器可以执行其他协程。

    作者回复: 谢谢那时刻的分享

    2020-05-12
    3
    10
  • myrfy
    “自旋锁开销少,在多核系统下一般不会主动产生线程切换,很适合异步、协程等在用户态切换请求的编程方式,有助于高并发服务充分利用多颗 CPU。“ 这段描述是不是不太准确?协程在用户态由协程框架调度,自旋锁只会阻塞当前协程,导致其他协程不能获得执行权。 协程框架下的sleep等函数,都是框架提供的而不是操作系统提供的,其工作原理是将当前协程的唤醒时间告知协程框架调度器后主动让出当前协程,让其他协程有机会运行,当每一个协程发生主动切换时,协程框架会检测是否有等待唤醒的协程,若有则会按照一定的策略唤醒睡眠的协程。

    作者回复: 你好myrfy,谢谢你的提醒,这里确实没有表达清楚。协程用户态是不会用到自旋锁的,协程框架的锁常用CAS函数实现,当时想表达是这个意思,这里确实会让人误解,我马上联系编辑修改下。

    2020-05-12
    7
  • abc
    老师,我有一个问题请教一下,就是多线程对一个数组进行读写,除了加锁之外,怎么深度优化呢?

    作者回复: 深度优化通常都与业务强相关。举个例子,当多个线程是基于范围(非争抢)消费数据时,那么有可能(或者就设计成这样)每个线程只处理数组中的部分数据,线程间互相不重合,这样就无须加锁。

    2020-08-05
    2
    5
  • Robust
    自旋锁属于悲观锁吗?

    作者回复: 算,需要事先拿到锁才能修改数据的都算

    2020-05-20
    5
    2
  • test
    协程有些操作是使用线程池来实现的,需要加锁

    作者回复: 操作共享资源的代码段都要加锁

    2020-05-11
    2
  • 范闲
    用户态的协程不能用互斥或者自旋,会进入内核态与其设计初衷相悖。Python里面用的yield

    作者回复: 你好范闲,很久以前追过庆余年这部小说,好熟的名字。 是的,用户态协程需要用户态的代码,将锁重新实现一遍,其中实现时不能用到内核提供的系统调用

    2020-05-18
    1
  • 而立斋
    看的很带劲,就好像吃了个美妙的西瓜。

    作者回复: ^_^

    2020-05-13
    1
  • 战斗机二虎🐯
    发现这段自旋锁代码用的nginx的实现😁

    作者回复: 没错^_^

    2020-06-23
  • 小喵喵
    老师,锁为什么分类那么多,是从哪些维度来分的,比如从数据库来分,可以分为悲观和乐观锁,以及为什么这么分类呢?

    作者回复: 主要是为了提升性能,当场景不同时,特别是加、解锁的成本不同,锁调用的频率不同时,换把合适的锁可以提升很大性能

    2020-05-12
收起评论
显示
设置
留言
28
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部