并发与锁

七赤九紫星 · · 1675 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

并发与锁

JUC AQS

AQS整体结构

锁的获取过程

  • CAS rote 回转数的问题

获取失败挂起的过程

释放锁唤醒等待的过程

  • 如何防止丢失唤醒

    入队时不能“贪睡”,找可靠等待者并让他叫醒自己,不在进而就获取锁,其他所有情况都告诉标记前驱别忘了叫醒自己

    这就够了吗,显然还不行;满足自己去“睡”的条件的check和“睡”显然不是一个原子操作。在check后和“睡着”前如果条件变了就没人叫醒

    unpark和park早就预防相关问题,猜想内部是有变量记忆了上次的操作后状态,同时基于操作系统提供的锁保证了原子性

  • 如何防止惊群问题

    锁的唤醒通常是有唤醒一个等待线程和唤醒全部等待线程的;通常在编程条件下如果想减少全部唤醒的引发不必要的竞争时,还要注意虚假

    唤醒问题,被唤醒的线程如果条件不满足条件释放锁后造成唤醒链脱节的问题。而这种情况又不能简单在自己释放锁前去唤醒其他等待者。

    所以比如在leader-follower模式中,如果leader线程只在原leader线程离任时唤醒任意一个等待线程时,则当线程都在follower模式

    时因为没有唤醒者陷入停机状态,所以在每次完成工作时还要尝试自荐为leader。如果leader在离任时唤起其他所有线程显然时惊群的。

AQS小结

  • 双检锁的简单优化
  • 流言:cas+clh队列语义上等价于锁

    线程阻塞于唤醒的闭合性无法满足,需要借助cpu指令中的屏蔽中断与禁止抢占保证调度切换的原子性

从CAS到内存屏障再到缓存一致性协议

  • volatile happens-before

    如果a线程先写入新值,b线程紧接着(赋值后隔一个时钟周期,这样可以使得无论是lock前缀的指令排空store buffer

    并且失效其他处理器的对于缓存得以执行完,或者时钟中断引发store buffer排空进而触发缓存一致性同步)去读就能

    最新值,不过由于需要间隔一个时钟周期,看似违反happens-before;不过由于紧接着是lock的情况在紧接着的时钟

    周期里由于lock触发缓存一致性同步会失效该数据项的旧值所以不会有问题,时钟中断的间接触发缓存同步也类似。

  • unsafe 源码

https://github.com/dmlloyd/openjdk/blob/jdk/jdk/src/hotspot/share/prims/unsafe.cpp

  • CAS 指令与总线锁 CMPXCHG16B
  • 内存屏障与缓存一致性协议
  • 编译器优化、cpu乱序执行【关于体系结构只针对X86-64其他乱序自由度更大的暂不考虑,料想其原理是类似的】

冒险:

数据冒险
结构冒险
控制冒险(分支冒险)
MESI、MOSI、MESIF

一图胜过千言(待添加),

硬件结构: 写缓存的读取优先与L1cache,写合并WC优化;写回缓存WB,写穿透缓存WT,

写失效WI,经验表明写失效,更节约带宽

写更新WU,看起来快,有时候是不必要的如果更新每个缓存比起一条失效指令代价大许多;

更新可能是断续多次而失效只要一次,而且缓存行内容的大小比缓存地址大位宽大不少

X86-64一致性高,LoadLoad Memory Barrier是因为读屏障会强制等待Invalidate Queue清空才继续执行,

这样可以消除其引发的读乱序问题

  • 缓存一致性协议提供了缓存一致性的必要的手段和机制,内存屏障是通过简单的抑制乱序以及对这些手段和机制的利用

    进而达到缓存一致的对外表现。这里提现硬件的对外复杂度的隔离封装,对外暴露了简单的接口,内部实现像水面下的

    冰山。

缓存一致性协议显然在硬件上有锁的语义的

loadload

loadstore

storeload (x86 TSO 唯一乱序的地方)

storestore

Synchronized

  • 对象头

  • 锁升级

  • 安全点及延申

    精确式GC、安全点调度以及协程调度java与golang

Futex

Futex是一种用户态和内核态混合的同步机制。首先,同步的进程间通过mmap共享一段内存,futex变量就位于这段共享 的内存中且操作是原子的,当进程尝试进入互斥区或者退出互斥区的时候,先去查看共享内存中的futex变量,如果没有竞争发生,则只修改futex,而不 用再执行系统调用了。当通过访问futex变量告诉进程有竞争发生,则还是得执行系统调用去完成相应的处理(wait 或者 wake up)。简单的说,futex就是通过在用户态的检查,(motivation)如果了解到没有竞争就不用陷入内核了,大大提高了low-contention时候的效率。 Linux从2.5.7开始支持Futex。
  • 自旋锁

    内核的基本锁之一

  • 自旋优化(jdk9与自旋提示)

自旋是在循环中不断重试检测条件变量的值,这样为了保证可见性,就需要不断的同步缓存,造成很多不必要的开销。

  • RCU锁

    包含的数据通常都是指针类型的,每个核上都发生过调度就说明如果其他核在该rcu锁的读临界区,完成一轮调度后(读临界区不可抢占)

    如果再有新的线程可见的数据是新的老版本的可以释放了。如果是可抢占的RCU就需要在锁定开头和释放的结尾维护一个计数器。

  • 屏蔽中断与禁止抢占

锁的构成要素

>cas
>等待队列
>屏蔽中断禁止抢占的原子调度上下文的切换

理解与体会

  • 不总结不准备就会拔剑四顾心茫然

  • 读写时序

  • 控制流与数据流的等价性

  • 几种并发模型 actor、CSP、CPS/CallCC
  • 锁的本质问题是时序问题,调度可以解决时序问题,调度问题从cpu角度看又是执行权的问题,锁是保护了数据,锁是通过限制代码的执行来保护数据的。

mesi为什么可以保证一致性对分布式环境缓存一致性有什么启示

首先总线的作用是巨大的,他保证了多核乃至多路cpu间的缓存一致性

分布式环境下由于没有统一的总线,缺少收敛的协调控制。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:七赤九紫星

查看原文:并发与锁

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1675 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传