本文用于理解各種自旋鎖對(duì)于緩存一致性風(fēng)暴進(jìn)行的優(yōu)化
案例一: TAS鎖引起的緩存一致性風(fēng)暴
TAS鎖.png
假設(shè)機(jī)器是64位的晤碘,CPU一次讀取64bit馍刮,即8個(gè)字節(jié)的數(shù)據(jù)高速緩存L1的一個(gè)緩存塊开睡;有2個(gè)CPU,每個(gè)CPU都有一個(gè)L1高速緩存;4個(gè)線程裹虫,一次為T1,T2,T3,T4
飞盆;CPU1執(zhí)行線程T1娄琉,CPU2執(zhí)行線程T2,CPU1執(zhí)行線程T3吓歇,CPU2執(zhí)行線程T4孽水;4個(gè)線程競(jìng)爭(zhēng)鎖的順序是T1->T2->T3->T4,則
- 當(dāng)線程1在CPU1上執(zhí)行
while(state.getAndSet(true))
時(shí)城看,即state
記錄的共享地址讀取true
值到L1的一個(gè)緩存塊女气,記錄該緩存塊d11的狀態(tài)為exclusive; - 當(dāng)線程2在CPU2執(zhí)行
while(state.getAndSet(true))
時(shí)析命,CPU1檢測(cè)到地址沖突主卫,如果CPU1的L1里記錄state
的緩存塊的狀態(tài)已經(jīng)是shared逃默,則不做操作;否則將CPU1的L1里記錄state
的緩存塊的狀態(tài)修改為shared簇搅。CPU2從state
記錄的共享地址讀取true
值到L1的一個(gè)緩存塊完域,記錄該緩存塊的狀態(tài)為shared; - 當(dāng)線程3在CPU1執(zhí)行
while(state.getAndSet(true))
時(shí)瘩将,由于CPU1的L1里記錄state
的緩存塊的狀態(tài)已經(jīng)是shared吟税,所以不做操作。線程4的情況類(lèi)似姿现,就省略肠仪; - 當(dāng)線程1在CPU1上執(zhí)行
state.set(false)
時(shí),修改L1里記錄該state
值的緩存塊的狀態(tài)為modified备典,并發(fā)起“修改各自高速緩存中記錄共享數(shù)據(jù)的緩存塊狀態(tài)為invalid”的廣播請(qǐng)求异旧。CPU2收到該廣播請(qǐng)求后,就將其上的L1里記錄共享數(shù)據(jù)state
的緩存塊狀態(tài)為invalid提佣; - 當(dāng)線程2在CPU2執(zhí)行
while(state.getAndSet(true))
時(shí)吮蛹,發(fā)現(xiàn)其L1中記錄該state
值的緩存塊的狀態(tài)為invalid,然后發(fā)起“從主內(nèi)中讀取共享數(shù)據(jù)”的廣播請(qǐng)求拌屏。CPU1收到該廣播請(qǐng)求后潮针,將其上的L1里記錄state
值的緩存塊的數(shù)據(jù)false
刷新到主內(nèi)存,并修改該緩存塊的狀態(tài)為shared倚喂。CPU2則從主內(nèi)存中讀取更新后的state
值false
到L1的一個(gè)緩存塊每篷,記錄該緩存塊的狀態(tài)為shared; - 線程2釋放鎖端圈,線程3獲取鎖焦读;線程3釋放鎖,線程4獲得鎖枫笛;線程4釋放鎖都是在重復(fù)步驟4和步驟5
案例二:ArrayLock(有界隊(duì)列鎖)帶來(lái)的緩存一致性風(fēng)暴優(yōu)化
Array-Lock-1.png
情形一:假定boolean數(shù)據(jù)占據(jù)1個(gè)字節(jié)吨灭,一個(gè)CPU執(zhí)行一個(gè)線程,此時(shí)的緩存一致風(fēng)暴量跟TAS鎖一樣刑巧,就不做討論了
情形二:假定boolean數(shù)據(jù)占據(jù)1個(gè)字節(jié)喧兄,2個(gè)CPU,4個(gè)線程:CPU1執(zhí)行線程T1啊楚、T3吠冤,CPU2執(zhí)行線程T2、T4恭理,
- 當(dāng)線程T1在CPU1上執(zhí)行
while(!flags[0]){}
時(shí)拯辙,會(huì)一次讀取8個(gè)字節(jié)的數(shù)據(jù)到L1的一個(gè)緩存塊中,并標(biāo)記該緩存塊的狀態(tài)為exclusive; - 當(dāng)線程T2在CPU2上執(zhí)行
while(!flags[1]){}
時(shí)涯保,CPU1檢測(cè)到地址沖突诉濒,如果L1中存儲(chǔ)flags[1]值的緩存塊的狀態(tài)已經(jīng)是shared,則不做操作夕春;否則將L1存儲(chǔ)flags[1]值的緩存塊的狀態(tài)修改為shared未荒;CPU2會(huì)一次讀取8個(gè)字節(jié)的數(shù)據(jù)到高速緩存L2的一個(gè)緩存塊中,并標(biāo)記該緩存塊的狀態(tài)為shared及志; - 當(dāng)線程T3在CPU1上執(zhí)行
while(!flags[2]){}
時(shí)片排,由于在步驟1中已經(jīng)加載了flags[2]的數(shù)據(jù),所以讀命中速侈,此時(shí)L1中記錄flags[2]的狀態(tài)為shared率寡; - 當(dāng)線程T4在CPU2上執(zhí)行
while(!flags[3]){}
時(shí),由于在步驟1中已經(jīng)加載了flags[3]的數(shù)據(jù)倚搬,所以讀命中冶共,此時(shí)L2中記錄flags[3]的狀態(tài)為shared; - 當(dāng)線程1在CPU1執(zhí)行
flags[0]=false;flags[1]=true
時(shí)每界,則將flags[0]和flags[1]所在的緩存塊的狀態(tài)改為modified,并發(fā)起“修改各自記錄共享數(shù)據(jù)的緩存塊的狀態(tài)為invalid”的廣播請(qǐng)求比默。CPU2收到該廣播請(qǐng)求后,就將各自高速緩存中記錄共享數(shù)據(jù)的緩存塊的狀態(tài)為invalid盆犁; - 線程2在CPU2執(zhí)行
while(!flags[1]){}
時(shí),發(fā)現(xiàn)高速緩存L2中記錄flags[1]的緩存塊的狀態(tài)為invalid篡九,則就發(fā)起“從主內(nèi)存中讀取數(shù)據(jù)”的廣播請(qǐng)求谐岁,此時(shí)CPU1就把L1記錄的flag[1]所在的緩存塊的數(shù)據(jù)刷新到主內(nèi)存,并記錄該緩存塊的狀態(tài)改為shared榛臼;CPU2從主內(nèi)存中重新讀取flags[1]的數(shù)據(jù)到L2的一個(gè)緩存行伊佃,并記錄該緩存行的狀態(tài)為shared - 當(dāng)線程2在CPU2上執(zhí)行
flags[1]=false;flags[2]=true
時(shí),則將flags[1]和flags[2]所在的緩存塊的狀態(tài)改為modified,并發(fā)起“修改各自記錄共享數(shù)據(jù)的緩存塊的狀態(tài)為invalid”的廣播請(qǐng)求沛善。CPU1收到該廣播請(qǐng)求后航揉,就將各自高速緩存中記錄共享數(shù)據(jù)的緩存塊的狀態(tài)為invalid; - 線程3在CPU1執(zhí)行
while(!flags[2]){}
時(shí)金刁,發(fā)現(xiàn)高速緩存L2中記錄flags[2]的緩存塊的狀態(tài)為invalid帅涂,則就發(fā)起“從主內(nèi)存中讀取數(shù)據(jù)”的廣播請(qǐng)求,此時(shí)CPU2就把L2記錄的flag[2]所在的緩存塊的數(shù)據(jù)刷新到主內(nèi)存尤蛮,并記錄該緩存塊的狀態(tài)改為shared媳友;CPU1從主內(nèi)存中重新讀取flags[2]的數(shù)據(jù)到L1的一個(gè)緩存行,并記錄該緩存行的狀態(tài)為shared
參考資料
- 《The Art of Multi-Processor Programming》
- 聊聊高并發(fā)(三十四)Java內(nèi)存模型那些事(二)理解CPU高速緩存的工作原理
http://blog.csdn.net/iter_zc/article/details/41979189 - 聊聊高并發(fā)(五)理解緩存一致性協(xié)議以及對(duì)并發(fā)編程的影響
http://blog.csdn.net/iter_zc/article/details/40342695