從多核CPU Cache一致性的應用到分佈式系統一致性的概念遷移

概述

      現代多核CPU的cache模型基本都跟下圖1所示同樣,L1 L2 cache是每一個核獨佔的,只有L3是共享的,當多個cpu讀、寫同一個變量時,就須要在多個cpu的cache之間同步數據,跟分佈式系統同樣,必然涉及到一致性的問題,只不過二者之間共享內容的方式不同而已,一個經過共享內存來共享內容,另外一個經過網絡消息傳遞來共享內容。就像wiki所說起的:c++

Interestingly enough, a shared-memory multiprocessor system really is a message-passing computer under the covers. This means that clusters of SMP machines that use distributed shared memory are using message passing to implement shared memory at two different levels of the system architecture.緩存

 

 圖一、現代cpu多級cache安全

多核一致性與原子操做

        多核一致性最典型的應用場景是多線程的原子操做,其在多線程開發中常常用到,好比在計數器的生成,這類狀況下數據有併發的危險,可是用鎖去保護又顯得有些浪費,因此原子類型操做十分的方便。網絡

        原子操做雖然用起來簡單,可是其背景遠比咱們想象的要複雜。其主要在於現代計算系統過於的複雜:多處理器、多核處理器、處理器又有核心獨有以及核心共享的多級緩存,在這種狀況下,一個核心修改了某個變量,其餘核心何時可見是一個十分嚴肅的問題。同時在極致最求性能的時代,處理器和編譯器每每表現的很智能,進行極度的優化,好比什麼亂序執行、指令重排等,雖然能夠在當前上下文中作到很好的優化,可是放在多核環境下經常會引出新的問題來,這時候就必須提示編譯器和處理器某種提示,告訴某些代碼的執行順序不能被優化。今天咱們重點看一下處理器在多線程原子操做上的背景原理以及具體應用。多線程

CPU Cache與內存屏障

     考慮下面典型的代碼:併發

-Thread 1-
void foo(void)
{
   a = 1;
   b = 1;
}
-Thread 2-
void bar(void)
{
   while (b == 0) continue;
   assert(a == 1);
}

因爲cpu cache的存在,thread 2在斷言處可能會失敗。具體的,因爲各個CPU的cache是獨立的,因此變量在他們各自的cache裏面的順序可能跟代碼的順序是不一致的,也就是說執行thread2的cpu可能會先看到變量b的變化,而後再看到變量a的變化,致使斷言失敗。就是咱們常見的program order與process order的不一致的工程現象,這裏就涉及到了memory consistency model的問題(相似於分佈式系統的一致性)。app

       上述的代碼若是要正確執行,則變量a、b之間須要有‘happen before’的語義來約束(這裏就能夠聯想到分佈式系統中因果一致性的概念)。可是對於這個語義上的需求,硬件設計者也心有餘而力不足,由於CPU沒法知道變量之間的關聯關係。因此硬件設計者提供了memory barrier指令,讓軟件能夠經過這些指令來告訴CPU這類關係,實現program order與process order的順序一致。相似於下面的代碼:分佈式

-Thread 1-
void foo(void)
{
    a = 1;
    memory_barrier();
    b = 1;
}

增長memory barrier以後,就能夠保證在執行b=1的時候,cpu已經處理過'a=1'的操做了。也就是說經過硬件提供的memory barrier語義,使得軟件可以保證其以前的內存訪問操做先於其後的完成。memory barrier 經常使用的地方包括:實現內核的鎖機制、應用層編寫無鎖代碼、原子變量等。下面咱們一塊兒看下,c++11是怎樣使用內存屏障來實現原子操做的。函數

C++11的原子操做

        在C++11標準出來以前,C++標準沒有一個明確的內存模型,各個C++編譯器實現者各自爲政,隨着多線程開發的普及解決這個問題變得愈來愈迫切。在標準出來以前,GCC的實現是根據Intel的開發手冊搞出的一系列的__sync原子操做函數集合,具體以下:性能

type __sync_fetch_and_OP (type *ptr, type value, ...)
type __sync_OP_and_fetch (type *ptr, type value, ...)
bool__sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...)
type __sync_val_compare_and_swap (type *ptr, type oldval, type newval, ...)
__sync_synchronize (...)

       在C++11新標準中規定的內存模型(memory model)顆粒要比上述的內存模型細化不少,因此軟件開發者就有不少的操做空間了,若是熟悉這些內存模型,在保證業務正確的同時能夠將對性能的影響減弱到最低,在硬件資源吃緊的地方,這是咱們優化程序的一個重要方向。

       咱們以c++11的原子變量的保證來展開這些內存模型。原子變量的通用接口使用store()和load()方式進行存取,能夠額外接受一個額外的memory order參數,這個參數就是對應了c++11的內存模型,根據執行線程之間對變量的同步需求強度,新標準下的內存模型能夠分紅以下幾類:

Sequentially Consistent

      該模型是最強的同步模式,參數表示爲std::memory_order_seq_cst,同時也是默認的模型。

-Thread 1-
y = 1
x.store (2); 

-Thread2-
if(x.load() ==2)
assert (y ==1)

       對於上面的例子,即便x和y是不相關的,一般狀況下處理器或者編譯器可能會對其訪問進行重排,可是在seq_cst模式下,x.store(2)以前的全部memory accesses都發生在store操做以前。同時,x.load()以後的全部memory accesses都發生在load()操做以後,也就是說seq_cst模式下,內存的限制是雙向的。

Acquire/Release Consistent

std::atomic<int> a{0};
intb =0;
-Thread 1- b = 1; a.store(1, memory_order_release); -Thread 2- while(a.load(memory_order_acquire) !=1)/*waiting*/; std::cout<< b <<'\n';

       毫無疑問,若是是memory_order_seq_cst內存模型,那麼上面的操做必定是成功的(打印變量b顯示爲1)。

       1. memory_order_release保證在這個操做以前的memory accesses不會重排到這個操做以後去,可是這個操做以後的memory accesses可能會重排到這個操做以前去。一般這個主要是用於以前準備某些資源後,經過store+memory_order_release的方式」Release」給別的線程;

       2. memory_order_acquire保證在這個操做以後的memory accesses不會重排到這個操做以前去,可是這個操做以前的memory accesses可能會重排到這個操做以後去。一般經過load+memory_order_acquire判斷或者等待某個資源,一旦知足某個條件後就能夠安全的「Acquire」消費這些資源了。

      這個就是相似於分佈式系統的因果一致性的概念。

Relaxed Consistent

       這個是最寬鬆的模式,memory_order_relaxed沒有happens-before的約束,編譯器和處理器能夠對memory access作任何的re-order,所以另外的線程不能對其作任何的假設,這種模式下能作的惟一保證,就是一旦線程讀到了變量var的最新值,那麼這個線程將再也見不到var修改以前的值了(這個相似於分佈式系統單調讀保證的概念)。

       這種狀況一般是在須要原子變量,可是不在線程間同步共享數據的時候會用,同時當relaxed存一個數據的時候,另外的線程將須要一個時間才能relaxed讀到該值(也就是最終若是變量再也不更改的話,全部的線程仍是能夠讀取到變量最終的值的),在非緩存一致性的構架上須要刷新緩存。在開發的時候,若是你的上下文沒有共享的變量須要在線程間同步,選用Relaxed就能夠了。

       這一點相似於分佈式系統的最終一致性概念了。

總結

      上述的過程體現的是強一致性、因果一致性、最終一致性等概念在c++11原子操做的使用,以及當前技術圈很是熱門的話題分佈式系統開發中分佈式一致性概念的思考與遷移。從中咱們能夠看出技術在發展,可是不少概念實際上是一脈相承的,只有深入理解了概念背後的原理以及相關技術發展的背景,才能勉強跟上技術的發展浪潮。

相關文章
相關標籤/搜索