緩存一致性問題
當程序在運行過程中秽褒, 會將運算需要的數(shù)據(jù)從主存復制一份到 CPU 的高速緩存當中, 那么 CPU 進行計算時就可以直接從它的高速緩存讀取數(shù)據(jù)和向其中寫入數(shù)據(jù)具篇, 當運算結(jié)束之后盯腌, 再將高速緩存中的數(shù)據(jù)刷新到主存當中沃斤。 舉個簡單的例子弓摘, 比如下面的這段代碼:
i = i+1
當線程執(zhí)行這個語句時焚鹊, 會先從主存當中讀取 i 的值, 然后復制一份到高速緩存當中韧献, 然后 CPU 執(zhí)行指令對 i 進行加 1 操作末患, 然后將數(shù)據(jù)寫入高速緩存,最后將高速緩存中 i 最新的值刷新到主存當中锤窑。
這個代碼在單線程中運行是沒有任何問題的璧针, 但是在多線程中運行就會有問題了。 在多核 CPU 中渊啰, 每條線程可能運行于不同的 CPU 中探橱, 因此每個線程運行時有自己的高速緩存(對單核 CPU 來說, 其實也會出現(xiàn)這種問題虽抄, 只不過是以線程調(diào)度的形式來分別執(zhí)行的) 。
本文我們以多核 CPU 為例
比如同時有 2 個線程執(zhí)行這段代碼独柑, 假如初始時 i 的值為 0迈窟, 那么我們希望兩個線程執(zhí)行完之后 i 的值變?yōu)?2。 但是事實會是這樣嗎忌栅?
可能存在下面一種情況: 初始時车酣, 兩個線程分別讀取 i 的值存入各自所在的CPU 的高速緩存當中, 然后線程 1 進行加 1 操作索绪, 然后把 i 的最新值 1 寫入到內(nèi)存湖员。 此時線程 2 的高速緩存當中 i 的值還是 0, 進行加 1 操作之后瑞驱, i 的值為1娘摔, 然后線程 2 把 i 的值寫入內(nèi)存。
最終結(jié)果 i 的值是 1唤反, 而不是 2凳寺。 這就是著名的緩存一致性問題鸭津。 通常稱這種被多個線程訪問的變量為共享變量。
也就是說肠缨, 如果一個變量在多個 CPU 中都存在緩存(一般在多線程編程
時才會出現(xiàn)) 逆趋, 那么就可能存在緩存不一致的問題
如何解決緩存一致性的問題
為了解決緩存不一致性問題, 通常來說有以下 2 種解決方法:
1) 通過在總線加 LOCK#鎖的方式
2) 通過緩存一致性協(xié)議
通過在總線加 LOCK#鎖的方式
在早期的 CPU 當中晒奕, 是通過在總線上加 LOCK#鎖的形式來解決緩存不一致的問題闻书。 因為 CPU 和其他部件進行通信都是通過總線來進行的, 如果對總線加 LOCK#鎖的話脑慧, 也就是說阻塞了其他 CPU 對其他部件訪問(如內(nèi)存) 魄眉,從而使得只能有一個 CPU 能使用這個變量的內(nèi)存。 比如上面例子中 如果一個線程在執(zhí)行 i = i +1漾橙, 如果在執(zhí)行這段代碼的過程中杆融, 在總線上發(fā)出了 LCOK#鎖的信號, 那么只有等待這段代碼完全執(zhí)行完畢之后霜运, 其他 CPU 才能從變量 i所在的內(nèi)存讀取變量脾歇, 然后進行相應的操作。 這樣就解決了緩存不一致的問題淘捡。但是上面的方式會有一個問題藕各, 由于在鎖住總線期間, 其他 CPU 無法訪問內(nèi)存焦除, 導致效率下激况。
但是上面的方式會有一個問題, 由于在鎖住總線期間膘魄, 其他 CPU 無法訪問內(nèi)存乌逐, 導致效率低下。
通過緩存一致性協(xié)議
所以就出現(xiàn)了緩存一致性協(xié)議创葡。 該協(xié)議保證了每個緩存中使用的共享變量的副本是一致的浙踢。
它核心的思想是: 當 CPU 向內(nèi)存寫入數(shù)據(jù)時, 如果發(fā)現(xiàn)操作的變量是共享變量灿渴, 即在其他 CPU 中也存在該變量的副本洛波, 會發(fā)出信號通知其他 CPU 將該變量的緩存行置為無效狀態(tài), 因此當其他 CPU 需要讀取這個變量時骚露, 發(fā)現(xiàn)自己緩存中緩存該變量的緩存行是無效的蹬挤, 那么它就會從內(nèi)存重新讀取。