1. 程式人生 > >什麼是快取一致性問題?如何解決?

什麼是快取一致性問題?如何解決?

當程式在執行過程中,會將運算需要的資料從主存複製一份到cup的快取記憶體當中,那麼cpu進行計算時就可以直接從它的快取記憶體讀取資料和向其中寫入資料,當運算結束後,再將快取記憶體中的資料重新整理到主存當中。舉個簡單的例子,比如下面這段程式碼:

i=i+1
  • 1

當執行緒執行這個語句時,會先從快取當中讀取i的值,然後複製一份到快取記憶體當中,然後CPU執行命令對i進行加1操作,然後將資料寫入快取記憶體。最後將快取記憶體中的最新的值重新整理到主存當中。

這個程式碼在單執行緒中執行時沒有任何問題的,但是在多執行緒中執行就會有問題了。在多核CPU中,每條執行緒可能運行於不同的CPU中,因此每個執行緒執行時有自己的快取記憶體(對單核CPU來說,其實也會出現這種問題,只不過是以執行緒排程的形式來分別執行的)。我們以多喝CPU為例。

可能存在下面一種情況:初始時,兩個執行緒分別讀取i的值存入各自所在的CPU的快取記憶體當中,然後執行緒1進行加1操作,然後把i的最新值1寫入到記憶體。此時執行緒2的快取記憶體當中i的值還是0,進行加1操作之後,i的值為1,然後執行緒2把i的值寫入記憶體。

最終結果i的值是1,而不是2.這就是快取一致性問題。通常稱這種被多個執行緒訪問的變數為共享變數。

解決快取一致性問題的方法有一下2種:

  1. 通過在匯流排加LOCK#鎖的方式
  2. 通過快取一致性協議

在早期的CPU當中,是通過在總線上加LOCK#鎖的形式來解決快取不一致的問題。因為CPU和其他部件進行通訊都是通過匯流排來進行的,如果對匯流排加LOCK#鎖的話,也就是說阻塞了其他CPU對其他部件訪問,從而使得只有一個CPU能使用這個變數的記憶體。比如上面例子中,如果一個執行緒在執行i=i+1,如果在執行這段程式碼的過程中,在總線上發出了LOCK#鎖的訊號,那麼只有等待這段程式碼完全執行完畢之後,其他CPU才能從變數i所在的記憶體讀取變數,然後進行相應的操作。這樣就解決了快取不一致問題。

但是上面的方式會有一個問題,由於在鎖住匯流排期間,其他CPU無法訪問記憶體,導致效率低下。

所以就出現了快取一致性協議。該協議保證了每個快取中使用的共享變數的副本是一致的。它的核心思想是:當CPU向記憶體寫入資料時,如果發現操作的變數是共享變數,即在其他CPU中也存在該變數的副本,會發出訊號通知其他CPU將該變數的快取置為無效狀態,因此當其他CPU需要讀取這個變數時,發現自己快取中快取行是無效的,那麼它就會從記憶體重新讀取。java中的volatile就是該協議的實現,volatile的實現原理可參見volatile底層實現