1. 程式人生 > >從多核CPU Cache一致性的應用到分散式系統一致性的概念遷移

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

概述

      現代多核CPU的cache模型基本都跟下圖1所示一樣,L1 L2 cache是每個核獨佔的,只有L3是共享的,當多個cpu讀、寫同一個變數時,就需要在多個cpu的cache之間同步資料,跟分散式系統一樣,必然涉及到一致性的問題,只不過兩者之間共享內容的方式不一樣而已,一個通過共享記憶體來共享內容,另一個通過網路訊息傳遞來共享內容。就像wiki所提及的:

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.

 

 圖1、現代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的問題(類似於分散式系統的一致性)。

       上述的程式碼如果要正確執行,則變數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原子操作的使用,以及當前技術圈非常熱門的話題分散式系統開發中分散式一致性概念的思考與遷移。從中我們可以看出技術在發展,但是很多概念其實是一脈相承的,只有深刻理解了概念背後的原理以及相關技術發展的背景,才能勉強跟上技術的發展浪