【多線程與高併發原理篇:2_緩存一致性的解決方案】

1. 概述

上一篇拋出了一個緩存不一致問題,即多線程在多cpu執行過程中,各cpu高速緩存之間會出現數據不一致,或者cpu高速緩存與主內存數據不一致。從計算機的發展歷史看,解決緩存數據不一致,先後出現了兩種方案,一種是總線加鎖,該方案現在基本不用了,主要原因是性能差。第二種是目前主流的緩存一致性協議,也就是較爲著名的MESI協議。

2. 總線加鎖

cpu從主內存取數據到自己的高速緩存,或者將自己對應高速緩存數據同步刷新到主內存,都要經過總線。如下如所示:

如果線程2對flg=1要進行自增操作,首先cpu2要將主內存的數據flg=1讀到自己的高速緩存,此時總線會加鎖,然後再執行cpu2指令,將flg自增,並修改爲flg=2,隨後再寫回高速緩存,最後同步刷新到主內存,這一過程結束後,總線才釋放鎖。

在總線加鎖的過程中,cpu1就無法從主內存讀取數據,也無法將已修改完的數據從高速緩存同步刷新到主內存,直到cpu2對應的線程完成加鎖與釋放鎖的過程結後才能進行。

這個總線加鎖機制,性能差,一旦多個線程對某個共享變量進行操作,就會因總線加鎖導致線程串行化的問題,多個cpu多線程併發運行的時候,效率低。

3. 緩存一致性協議MESI

緩存一致性協議解決了上面總線加鎖性能差、效率低的問題。

該方案採用了對高速緩存中的數據基本單位緩存行(cache line)加鎖,而非通過總線加鎖,在實現過程中,JVM會向處理器發送一條Lock前綴指令,保證鎖的範圍限定在各cpu所在的高速緩存中,完成數據修改後,強制將數據刷新到主內存,這一過程中會給總線發送數據修改消息,其他cpu通過總線嗅探機制,嗅探到自己高速緩存中的數據已修改,會失效自己當前的高速緩存數據,重新將主內存的最新數據加載到自己緩存,以保證主內存與各高速緩存中的數據一致性。

下面通過圖例來說明:

線程1讀數據:
1-1: 線程1對主內存數據進行讀操作,將變量flg=1讀到高速緩存,供cpu計算使用;
1-2: cpu向總線發送嗅探消息,一旦有其他高速緩存向總線發送修改緩存的消息,就執行1-3步驟;
1-3: 失效自己高速緩存數據flg=1,將主內存中的最新數據重新讀入自己的高速緩存。

線程2寫數據:
2-1: 線程2對主內存數據進行自增操作,先讀取到高速緩存,再通過cpu指令完成自增操作後,寫回主高速緩存,該步驟會對自己的高速緩存中的最小單位緩存行加鎖
2-2: 最後將flg=2刷回主內存,數據經過總線時,會給總線發送數據修改消息;

上述過程中,如果線程1再次讀高速緩存的數據,會通過嗅探消息發現數據已更改,並確認得知數據也已經強制刷新到主內存中,做1-3步驟,失效自己的原先的flg=1,強制從主內存重新讀取數據到cpu1的高速緩存中。

MESI協議在不同硬件底層有不同的實現方式,但可以在效果上達到兩個目的:
(1)flush機制:對高速緩存的數據修改強制刷新到主內存;
(2)refresh機制:其他cpu如果嗅探到自己高速緩存的變量副本已被修改,失效自己的高速緩存,從主內存獲取最新數據到高速緩存。

對於MESI協議的具體實現,裏面還涉及到不少細節,將在後續的篇幅中慢慢展開。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章