自旋锁--缓存一致性风暴优化案例

本文用于理解各种自旋锁对于缓存一致性风暴进行的优化


案例一: TAS锁引起的缓存一致性风暴

自旋锁--缓存一致性风暴优化案例_第1张图片
TAS锁.png

假设机器是64位的,CPU一次读取64bit,即8个字节的数据高速缓存L1的一个缓存块;有2个CPU,每个CPU都有一个L1高速缓存;4个线程,一次为T1,T2,T3,T4;CPU1执行线程T1,CPU2执行线程T2,CPU1执行线程T3,CPU2执行线程T4;4个线程竞争锁的顺序是T1->T2->T3->T4,则

  1. 当线程1在CPU1上执行while(state.getAndSet(true))时,即state记录的共享地址读取true值到L1的一个缓存块,记录该缓存块d11的状态为exclusive;
  2. 当线程2在CPU2执行while(state.getAndSet(true))时,CPU1检测到地址冲突,如果CPU1的L1里记录state的缓存块的状态已经是shared,则不做操作;否则将CPU1的L1里记录state的缓存块的状态修改为shared。CPU2从state记录的共享地址读取true值到L1的一个缓存块,记录该缓存块的状态为shared;
  3. 当线程3在CPU1执行while(state.getAndSet(true))时,由于CPU1的L1里记录state的缓存块的状态已经是shared,所以不做操作。线程4的情况类似,就省略;
  4. 当线程1在CPU1上执行state.set(false)时,修改L1里记录该state值的缓存块的状态为modified,并发起“修改各自高速缓存中记录共享数据的缓存块状态为invalid”的广播请求。CPU2收到该广播请求后,就将其上的L1里记录共享数据state的缓存块状态为invalid;
  5. 当线程2在CPU2执行while(state.getAndSet(true))时,发现其L1中记录该state值的缓存块的状态为invalid,然后发起“从主内中读取共享数据”的广播请求。CPU1收到该广播请求后,将其上的L1里记录state值的缓存块的数据false刷新到主内存,并修改该缓存块的状态为shared。CPU2则从主内存中读取更新后的statefalse到L1的一个缓存块,记录该缓存块的状态为shared;
  6. 线程2释放锁,线程3获取锁;线程3释放锁,线程4获得锁;线程4释放锁都是在重复步骤4和步骤5

案例二:ArrayLock(有界队列锁)带来的缓存一致性风暴优化

自旋锁--缓存一致性风暴优化案例_第2张图片
Array-Lock-1.png

情形一:假定boolean数据占据1个字节,一个CPU执行一个线程,此时的缓存一致风暴量跟TAS锁一样,就不做讨论了

情形二:假定boolean数据占据1个字节,2个CPU,4个线程:CPU1执行线程T1、T3,CPU2执行线程T2、T4,

  1. 当线程T1在CPU1上执行while(!flags[0]){}时,会一次读取8个字节的数据到L1的一个缓存块中,并标记该缓存块的状态为exclusive
  2. 当线程T2在CPU2上执行while(!flags[1]){}时,CPU1检测到地址冲突,如果L1中存储flags[1]值的缓存块的状态已经是shared,则不做操作;否则将L1存储flags[1]值的缓存块的状态修改为shared;CPU2会一次读取8个字节的数据到高速缓存L2的一个缓存块中,并标记该缓存块的状态为shared
  3. 当线程T3在CPU1上执行while(!flags[2]){}时,由于在步骤1中已经加载了flags[2]的数据,所以读命中,此时L1中记录flags[2]的状态为shared
  4. 当线程T4在CPU2上执行while(!flags[3]){}时,由于在步骤1中已经加载了flags[3]的数据,所以读命中,此时L2中记录flags[3]的状态为shared
  5. 当线程1在CPU1执行flags[0]=false;flags[1]=true时,则将flags[0]和flags[1]所在的缓存块的状态改为modified,并发起“修改各自记录共享数据的缓存块的状态为invalid”的广播请求。CPU2收到该广播请求后,就将各自高速缓存中记录共享数据的缓存块的状态为invalid
  6. 线程2在CPU2执行while(!flags[1]){}时,发现高速缓存L2中记录flags[1]的缓存块的状态为invalid,则就发起“从主内存中读取数据”的广播请求,此时CPU1就把L1记录的flag[1]所在的缓存块的数据刷新到主内存,并记录该缓存块的状态改为shared;CPU2从主内存中重新读取flags[1]的数据到L2的一个缓存行,并记录该缓存行的状态为shared
  7. 当线程2在CPU2上执行flags[1]=false;flags[2]=true时,则将flags[1]和flags[2]所在的缓存块的状态改为modified,并发起“修改各自记录共享数据的缓存块的状态为invalid”的广播请求。CPU1收到该广播请求后,就将各自高速缓存中记录共享数据的缓存块的状态为invalid
  8. 线程3在CPU1执行while(!flags[2]){}时,发现高速缓存L2中记录flags[2]的缓存块的状态为invalid,则就发起“从主内存中读取数据”的广播请求,此时CPU2就把L2记录的flag[2]所在的缓存块的数据刷新到主内存,并记录该缓存块的状态改为shared;CPU1从主内存中重新读取flags[2]的数据到L1的一个缓存行,并记录该缓存行的状态为shared

参考资料

  1. 《The Art of Multi-Processor Programming》
  2. 聊聊高并发(三十四)Java内存模型那些事(二)理解CPU高速缓存的工作原理
    http://blog.csdn.net/iter_zc/article/details/41979189
  3. 聊聊高并发(五)理解缓存一致性协议以及对并发编程的影响
    http://blog.csdn.net/iter_zc/article/details/40342695

你可能感兴趣的:(自旋锁--缓存一致性风暴优化案例)