如何正确有效地加锁

设计加锁代码从来都很难,让人进退两难的难。加锁过于简单粗暴(粗粒度),会让本来可以并行执行的程序性能下降;加锁太过精细复杂(细粒度),又增加锁操作的时间和空间开销,仍然会影响性能。前者造成死锁或饥饿,后者造成锁竞争。加锁本身还可能改变程序的执行流程,引入不确定性。

多线程、异步、加锁,这些东西很繁杂,所以很多时候人们宁愿使用单线程。可是尽管共享资源和互斥锁的多线程模型问题被认为存在缺陷,多线程和异步对程序的性能和可用性方面的好处明显到经常不得不用,到底有没有办法让加锁既正确又高效呢?

这个问题没有确定答案。正确的加锁方案往往是与程序逻辑息息相关的,恐怕没有通用的加锁方法。不过,即使我们没法了解所有程序的执行逻辑(甚至他们的共性),依然可以探讨拆解、归并和转化程序执行流程和锁的规则,优化、优化以逼近最好。这里谈谈我的理解和思考。

设想为了保证加锁的正确性,极端情况下如何加锁?程序全流程加一把大锁,程序变成串行了,保证正确。这在性能满足需求时未尝不可。但现实中,多线程的引入往往是因为性能不足,需要增加程序的并行度。于是,加锁方式需要优化,目的是把全流程大锁变成各程序片段的小锁,增加并行度。如何给程序分段呢?难以一步到位,就采取多次迭代的方法,按函数、分支、语义片段分段,不一而足,每次分段时确保代码段正确加锁(流程代入,相比之前的代码段状态不引入死锁或饥饿),分段测试代码段性能,总体性能下降则回撤到上一状态,尝试其他分段方法;性能提升则继续。运气好的话,每一次分段迭代加锁,测试性能顺利提升,直到代码段粒度足够小,发现性能再也不能提升(甚至下降),完成。

到这一步,只好摊手感叹说,锁优化就到这里了,我尽力了。

以上方法看上去很直观,可操作性好,因此容易普遍采用。可是细心的观众可能会发现方法有根本缺陷:程序分段没有章法,只靠事后性能测试对比,抓住老鼠就是好猫。加锁只关注局部代码段,局部迭代带来的优化和引入的开销,无法在全局程序体现出来。如果工程师对优化点和开销没有定量的理解(定性的了解都不够),那就更糟,后续优化只能靠猜。以至于到最后,关于加锁方式是否已经接近最优根本说不清道不明,很无奈很尴尬。

编码说到底是符合科学规律的工程活动。造成说不清道不明的状态肯定有原因,这里可以总结成两点:

  1. 代码分段没有章法,但对加锁结果影响重大,最终加锁效果随意性较大;

  2. 关注局部优化,对整体优化效果的影响不可知,无法判断是否全局最优。

回顾一下开篇说到加锁时提到的,加锁操作的场景——“共享资源和互斥锁的多线程模型”。对于互斥锁来说,有两个必要前提条件:共享资源和多线程。没有共享资源则无须加锁保护,程序单线程执行则资源独占,两种条件下均无须加锁。共享资源反映在代码中,即是文件、全局变量、堆内存等,以堆内存为代表,抽象为数据段;多线程反映在代码中,即是重叠执行的代码段,抽象为执行流。

我们以程序共享的数据段和执行流入手,来分析程序加锁的规则。首先,以执行流划分程序代码段,按执行顺序将程序代码段分成独立执行部分和重叠执行的部分。独立执行的部分无须加锁,各个重叠执行的部分是潜在地需要加锁的代码段,称为一个执行段。对于并行度较高的多线程程序,往往执行段也较大。这时,继续以数据段划分执行区间,将同一执行段划分为数据段(共享的文件描述符、全局变量、堆内存等)上下文互不干扰的代码段,称为执行节。在划分过程中,可在保证程序流程语义不变的情况调整执行流和数据段,同时清理无用的执行流和数据段,尽可能使得执行节较小(执行流短、数据段小)。

当程序流程划分为执行节之后,各个执行节之间在执行流程上的共享资源已经充分解耦,执行节上的优化效果可以分别反映到全局。在排除操作系统和运行时环境对程序的影响时,局部的优化提升意味着全局的优化提升。于是,我们就可以在执行节上分别做优化尝试(方法与多次迭代划分方法类似),测试优化提升的效果和加锁引入的开销,直到无法继续提升。各个执行节上做完优化之后,最后在程序全局做剖析和测试验证。

到这一步,我们就可以说,锁优化就到这里了,没有保留,我已经很满意了。

注: 本文所关注的锁特定指覆盖大多数应用场景的互斥锁,其他类型的锁不在本文篇幅考虑范围之内。其他加锁方法欢迎后台留言讨论。

参考资料:

  • 深入探索并发编程系列 http://www.chongh.wiki/categories/High-performance

  • 高性能服务器架构 http://pl.atyp.us/content/tech/servers.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值