国产精品久久久aaaa,日日干夜夜操天天插,亚洲乱熟女香蕉一区二区三区少妇,99精品国产高清一区二区三区,国产成人精品一区二区色戒,久久久国产精品成人免费,亚洲精品毛片久久久久,99久久婷婷国产综合精品电影,国产一区二区三区任你鲁

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

處理器架構下并發編程里必須掌握的基礎概念

lhl545545 ? 來源:Linuxer ? 作者:Linuxer ? 2020-06-09 15:02 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

現代計算機體系結構上,CPU執行指令的速度遠遠大于CPU訪問內存的速度,于是引入Cache機制來加速內存訪問速度。除了Cache以外,分支預測和指令預取也在很大程度上提升了CPU的執行速度。隨著SMP的出現,多線程編程模型被廣泛應用,在多線程模型下對共享變量的訪問變成了一個復雜的問題。于是我們有必要了解一下內存模型,這是多處理器架構下并發編程里必須掌握的一個基礎概念。

1. 什么是內存模型?

到底什么是內存模型呢?看到有兩種不同的觀點:

A:內存模型是從來描述編程語言在支持多線程編程中對共享內存訪問的順序。

B:內存模型的本質是指在單線程情況下CPU指令在多大程度上發生指令重排(reorder)[1]。

實際上A,B兩種說法都是正確的,只不過是在嘗試從不同的角度去說明memory model的概念。個人認為,內存模型表達為“內存順序模型”可能更加貼切一點。

一個良好的memory model定義包含3個方面:

Atomic Operations

Partial order of operations

Visable effects of operations

這里要強調的是:我們這里所說的內存模型和CPU的體系結構、編譯器實現和編程語言規范3個層面都有關系。

首先,不同的CPU體系結構內存順序模型是不一樣的,但大致分為兩種:

ArchitectureMemory Model

x86_64Total Store Order

SparcTotal Store Order

ARMv8Weakly Ordered

PowerPCWeakly Ordered

MIPSWeakly Ordered

x86_64和Sparc是強順序模型(Total Store Order),這是一種接近程序順序的順序模型。所謂Total,就是說,內存(在寫操作上)是有一個全局的順序的(所有人看到的一樣的順序), 就好像在內存上的每個Store動作必須有一個排隊,一個弄完才輪到另一個,這個順序和你的程序順序直接相關。所有的行為組合只會是所有CPU內存程序順序的交織,不會發生和程序順序不一致的地方[4]。TSO模型有利于多線程程序的編寫,對程序員更加友好,但對芯片實現者不友好。CPU為了TSO的承諾,會犧牲一些并發上的執行效率。

弱內存模型(簡稱WMO,Weak Memory Ordering),是把是否要求強制順序這個要求直接交給程序員的方法。換句話說,CPU不去保證這個順序模型(除非他們在一個CPU上就有依賴), 程序員要主動插入內存屏障指令來強化這個“可見性”[4]。ARMv8,PowerPC和MIPS等體系結構都是弱內存模型。每種弱內存模型的體系架構都有自己的內存屏障指令,語義也不完全相同。弱內存模型下,硬件實現起來相對簡單,處理器執行的效率也高, 只要沒有遇到顯式的屏障指令,CPU可以對局部指令進行reorder以提高執行效率。

對于多線程程序開發來說,對并發的數據訪問我們一般到做同步操作, 可以使用mutex,semaphore,conditional等重量級方案對共享數據進行保護。但為了實現更高的并發,需要使用內存共享變量做通信(Message Passing), 這就對程序員的要求很高了,程序員必須時時刻刻必須很清楚自己在做什么, 否則寫出來的程序的執行行為會讓人很是迷惑!值得一提的是,并發雖好,如果能夠簡單粗暴實現,就不要搞太多投機取巧!要實現lock-free無鎖編程真的有點難。

其次,不同的編程語言對內存模型都有自己的規范,例如:C/C++Java等不同的編程語言都有定義內存模型相關規范。

2011年發布的C11/C++11 ISO Standard為我們帶來了memory order的支持, 引用C++11里的一段描述:

The memory model means that C++ code now has a standardized

library to call regardless of who made the compiler and on

what platform it‘s running. There’s a standard way to control

how different threads talk to the processor‘s memory.[7]

memory order的問題就是因為指令重排引起的, 指令重排導致 原來的內存可見順序發生了變化, 在單線程執行起來的時候是沒有問題的, 但是放到 多核/多線程執行的時候就出現問題了, 為了效率引入的額外復雜邏輯的的弊端就出現了[8]。

C++11引入memory order的意義在于我們現在有了一個與運行平臺無關和編譯器無關的標準庫, 讓我們可以在high level languange層面實現對多處理器對共享內存的交互式控制。我們的多線程終于可以跨平臺啦!我們可以借助內存模型寫出更好更安全的并發代碼。真棒,簡直不要太優秀~

C11/C++11使用memory order來描述memory model, 而用來聯系memory order的是atomic變量, atomic操作可以用load()和release()語義來描述。一個簡單的atomic變量賦值可描述為:

atomic_var1.store (atomic_var2.load()); // atomic variables

vs

var1 = var2; // regular variables

為了更好地描述內存模型,有4種關系術語需要了解一下。

sequenced-before

同一個線程之內,語句A的執行順序在語句B前面,那么就成為A sequenced-before B。它不僅僅表示兩個操作之間的先后順序,還表示了操作結果之間的可見性關系。兩個操作A和操作B,如果有A sequenced-before B,除了表示操作A的順序在B之前,還表示了操作A的結果操作B可見。例如:語句A是sequenced-before語句B的。

r2 = x.load(std::memory_order_relaxed); // A

y.store(42, std::memory_order_relaxed); // B

happens-before

happens-before關系表示的不同線程之間的操作先后順序。如果A happens-before B,則A的內存狀態將在B操作執行之前就可見。happends-before關系滿足傳遞性、非自反性和非對稱性。happens before包含了inter-thread happens before和synchronizes-with兩種關系。

synchronizes-with

synchronizes-with關系強調的是變量被修改之后的傳播關系(propagate), 即如果一個線程修改某變量的之后的結果能被其它線程可見,那么就是滿足synchronizes-with關系的[9]。另外synchronizes-with可以被認為是跨線程間的happends-before關系。顯然,滿足synchronizes-with關系的操作一定滿足happens-before關系了。

Carries dependency

同一個線程內,表達式A sequenced-before 表達式B,并且表達式B的值是受表達式A的影響的一種關系, 稱之為“Carries dependency”。這個很好理解,例如:

int *a = &var1;

int *b = &var2;

c = *a + *b;

了解了上面一些基本概念,下面我們來一起學習一下內存模型吧。

2. C11/C++11內存模型

C/C++11標準中提供了6種memory order,來描述內存模型[6]:

enum memory_order {

memory_order_relaxed,

memory_order_consume,

memory_order_acquire,

memory_order_release,

memory_order_acq_rel,

memory_order_seq_cst

};

每種memory order的規則可以簡要描述為:

枚舉值定義規則

memory_order_relaxed不對執行順序做任何保證

memory_order_consume本線程中,所有后續的有關本原子類型的操作,必須在本條原子操作完成之后執行

memory_order_acquire本線程中,所有后續的讀操作必須在本條原子操作完成后執行

memory_order_release本線程中,所有之前的寫操作完成后才能執行本條原子操作

memory_order_acq_rel同時包含memory_order_acquire和memory_order_release標記

memory_order_seq_cst全部存取都按順序執行

下面我們來舉例一一說明,扒開內存模型的神秘面紗。

2.1 memory order releaxed

relaxed表示一種最為寬松的內存操作約定,Relaxed ordering 僅僅保證load()和store()是原子操作, 除此之外,不提供任何跨線程的同步[5]。

std::atomic《int》 x = 0; // global variable

std::atomic《int》 y = 0; // global variable

Thread-1: Thread-2:

r1 = y.load(memory_order_relaxed); // A r2 = x.load(memory_order_relaxed); // C

x.store(r1, memory_order_relaxed); // B y.store(42, memory_order_relaxed); // D

上面的多線程模型執行的時候,可能出現r2 == r1 == 42。要理解這一點并不難,因為CPU在執行的時候允許局部指令重排reorder,D可能在C前執行。如果程序的執行順序是 D -》 A -》 B -》 C,那么就會出現r1 == r2 == 42。

如果某個操作只要求是原子操作,除此之外,不需要其它同步的保障,那么就可以使用 relaxed ordering。程序計數器是一種典型的應用場景:

#include 《cassert》

#include 《vector》

#include 《iostream》

#include 《thread》

#include 《atomic》

std::atomic《int》 cnt = {0};

void f()

{

for (int n = 0; n 《 1000; ++n) {

cnt.fetch_add(1, std::memory_order_relaxed);

}

}

int main()

{

std::vector《std::thread》 v;

for (int n = 0; n 《 10; ++n) {

v.emplace_back(f);

}

for (auto& t : v) {

t.join();

}

assert(cnt == 10000); // never failed

return 0;

}

cnt是共享的全局變量,多個線程并發地對cnt執行RMW(Read Modify Write)原子操作。這里只保證cnt的原子性,其他有依賴cnt的地方不保證任何的同步。

2.2 memory order consume

consume要搭配release一起使用。很多時候,線程間只想針對有依賴關系的操作進行同步, 除此之外線程中其他操作順序如何不關心,這時候就適合用consume來完成這個操作。例如:

b = *a;

c = *b

第二行的變量c依賴于第一行的執行結果,因此這兩行代碼是“Carries dependency”關系。顯然,由于consume是針對有明確依賴關系的語句來限定其執行順序的一種內存順序, 而releaxed不提供任何順序保證, 所以consume order要比releaxed order要更加地Strong。

#include 《thread》

#include 《atomic》

#include 《cassert》

#include 《string》

std::atomic《std::string*》 ptr;

int data;

void producer()

{

std::string* p = new std::string(“Hello”);

data = 42;

ptr.store(p, std::memory_order_release);

}

void consumer()

{

std::string* p2;

while (!(p2 = ptr.load(std::memory_order_consume)))

;

assert(*p2 == “Hello”); // never fires: *p2 carries dependency from ptr

assert(data == 42); // may or may not fire: data does not carry dependency from ptr

}

int main()

{

std::thread t1(producer);

std::thread t2(consumer);

t1.join();

t2.join();

}

assert(*p2 == “Hello”)永遠不會失敗,但assert(data == 42)可能會。原因是:

p2和ptr直接有依賴關系,但data和ptr沒有直接依賴關系,

盡管線程1中data賦值在ptr.store()之前,線程2看到的data的值還是不確定的。

2.3 memory order acquire

acquire和release也必須放到一起使用。 release和acquire構成了synchronize-with關系,也就是同步關系。在這個關系下:線程A中所有發生在release x之前的值的寫操作, 對線程B的acquire x之后的任何操作都可見。

#include 《thread》

#include 《atomic》

#include 《cassert》

#include 《string》

#include 《iostream》

std::atomic《bool》 ready{ false };

int data = 0;

std::atomic《int》 var = {0};

void sender()

{

data = 42; // A

var.store(100, std::memory_order_relaxed); // B

ready.store(true, std::memory_order_release); // C

}

void receiver()

{

while (!ready.load(std::memory_order_acquire)) // D

;

assert(data == 42); // never failed // E

assert(var == 100); // never failed // F

}

int main()

{

std::thread t1(sender);

std::thread t2(receiver);

t1.join();

t2.join();

}

上面的例子中:

sender線程中data = 42是sequence before原子變量ready的

sender和receiver在C和D處發生了同步

線程sender中C之前的所有讀寫對線程receiver都是可見的 顯然, release和acquire組合在一起比release和consume組合更加Strong!

2.4 memory order release

release order一般不單獨使用,它和acquire和consume組成2種獨立的內存順序搭配。

這里就不用展開啰里啰嗦了。

2.5 memory order acq_rel

acq_rel是acquire和release的疊加。中文不知道該咋描述好:

A read-modify-write operation with this memory order is both an acquire operation and a release operation. No memory reads or writes in the current thread can be reordered before or after this store. All writes in other threads that release the same atomic variable are visible before the modification and the modification is visible in other threads that acquire the same atomic variable.

大致意思是:memory_order_acq_rel適用于read-modify-write operation, 對于采用此內存序的read-modify-write operation,我們可以稱為acq_rel operation, 既屬于acquire operation 也是release operation. 設有一個原子變量M上的acq_rel operation:自然的,該acq_rel operation之前的內存讀寫都不能重排到該acq_rel operation之后, 該acq_rel operation之后的內存讀寫都不能重排到該acq_rel operation之前。 其他線程中所有對M的release operation及其之前的寫入都對當前線程從該acq_rel operation開始的操作可見, 并且截止到該acq_rel operation的所有內存寫入都對另外線程對M的acquire operation以及之后的內存操作可見[13]。

這里是一個例子,關于為什么要有acq_rel可以參考一下:

#include 《thread》

#include 《atomic》

#include 《cassert》

#include 《vector》

std::vector《int》 data;

std::atomic《int》 flag = {0};

void thread_1()

{

data.push_back(42);

flag.store(1, std::memory_order_release);

}

void thread_2()

{

int expected=1;

while (!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel)) {

expected = 1;

}

}

void thread_3()

{

while (flag.load(std::memory_order_acquire) 《 2)

;

assert(data.at(0) == 42); // will never fire

}

int main()

{

std::thread a(thread_1);

std::thread b(thread_2);

std::thread c(thread_3);

a.join(); b.join(); c.join();

}

2.6 memory order seq_cst

seq_cst表示順序一致性內存模型,在這個模型約束下不僅同一個線程內的執行結果是和程序順序一致的, 每個線程間互相看到的執行結果和程序順序也保持順序一致。顯然,seq_cst的約束是最強的,這意味著要犧牲性能為代價。

atomic int x (0); atomic int y (0);

x. store (1, seq cst ); || y. store (1, seq cst );

int r1 = y.load( seq cst ); || int r2 = x.load( seq cst );

assert (r1 == 1 || r2 == 1);

下面是一個seq_cst的實例:

#include 《thread》

#include 《atomic》

#include 《cassert》

std::atomic《bool》 x = {false};

std::atomic《bool》 y = {false};

std::atomic《int》 z = {0};

void write_x()

{

x.store(true, std::memory_order_seq_cst);

}

void write_y()

{

y.store(true, std::memory_order_seq_cst);

}

void read_x_then_y()

{

while (!x.load(std::memory_order_seq_cst))

;

if (y.load(std::memory_order_seq_cst)) {

++z;

}

}

void read_y_then_x()

{

while (!y.load(std::memory_order_seq_cst))

;

if (x.load(std::memory_order_seq_cst)) {

++z;

}

}

int main()

{

std::thread a(write_x);

std::thread b(write_y);

std::thread c(read_x_then_y);

std::thread d(read_y_then_x);

a.join(); b.join(); c.join(); d.join();

assert(z.load() != 0); // will never happen

}

2.7 Relationship with volatile

人的一生總是充滿了疑惑。

可能你會思考?volatile關鍵字能夠防止指令被編譯器優化,那它能提供線程間(inter-thread)同步語義嗎?答案是:不能!!!

盡管volatile能夠防止單個線程內對volatile變量進行reorder,但多個線程同時訪問同一個volatile變量,線程間是完全不提供同步保證。

而且,volatile不提供原子性!

并發的讀寫volatile變量是會產生數據競爭的,同時non volatile操作可以在volatile操作附近自由地reorder。

看一個例子,執行下面的并發程序,不出意外的話,你不會得到一個為0的結果。

#include 《thread》

#include 《iostream》

volatile int count = 0;

void increase() {

for (int i = 0; i 《 1000000; i++) {

count++;

}

}

void decrease() {

for (int i = 0; i 《 1000000; i++) {

count--;

}

}

int main() {

std::thread t1(increase);

std::thread t2(decrease);

t1.join();

t2.join();

std::cout 《《 count 《《 std::endl;

}

3. Reference

The C/C++ Memory Model: Overview and Formalization

知乎專欄:如何理解C++的6種memory order

理解 C++ 的 Memory Order

理解弱內存順序模型

當我們在談論 memory order 的時候,我們在談論什么

https://en.cppreference.com/w/cpp/atomic/memory_order

Youtube: Atomic’s memory orders, what for? - Frank Birbacher [ACCU 2017]

C++11中的內存模型下篇 - C++11支持的幾種內存模型

memory ordering, Gavin’s blog

c++11 內存模型解讀

memory barriers in c, MariaDB FOUNDATION, pdf

C++ memory order循序漸進

Memory Models for C/C++ Programers

Memory Consistency Models: A Tutorial
責任編輯:pj

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • cpu
    cpu
    +關注

    關注

    68

    文章

    11281

    瀏覽量

    225079
  • 計算機
    +關注

    關注

    19

    文章

    7809

    瀏覽量

    93216
  • 內存模型
    +關注

    關注

    0

    文章

    7

    瀏覽量

    6226
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    TMS320LC548定點數字信號處理器詳解

    TMS320LC548定點數字信號處理器詳解 作為一名資深電子工程師,在數字信號處理領域摸爬滾打多年,我深知一款性能卓越的處理器對于項目的重要性。今天就來和大家深入探討一TI公司
    的頭像 發表于 03-09 09:50 ?269次閱讀

    SMJ320C80數字信號處理器架構、特性與應用全解析

    的運算能力和豐富的功能特性,成為了眾多工程師在設計中的理想選擇。今天,我們就來深入探討一SMJ320C80的架構、特性以及應用場景。 文件下載: sm320c80.pdf 一、處理器概述 SMJ320C80是一款單芯片的MIM
    的頭像 發表于 03-06 16:55 ?982次閱讀

    TAS3103A數字音頻處理器:特性、架構與應用詳解

    TAS3103A數字音頻處理器:特性、架構與應用詳解 引言 在當今數字化音頻處理領域,一款高性能、可配置的音頻處理器至關重要。德州儀器(Texas Instruments)的TAS31
    的頭像 發表于 02-27 16:25 ?131次閱讀

    嵌入式驅動開發,需要掌握哪些技能?

    處理器內核:理解處理器的內部結構,特別是寄存的使用,以及內存區域的用途,如堆、堆棧、IVT、代碼等。 熟悉外設接口:比如UART、AD、SPI、定時、PWM、實時時鐘等常見的外設接
    發表于 01-20 16:46

    MAX262微處理器編程通用有源濾波:設計與應用指南

    MAX260/MAX261/MAX262 微處理器編程通用有源濾波:設計與應用指南 在電子設計領域,濾波是信號處理中不可或缺的組件。M
    的頭像 發表于 01-20 11:05 ?284次閱讀

    進程概念和特征

    進程的概念   在多道程序環境,允許多個程序并發執行,此時它們將失去封閉性,并具有間斷性及不可再現性的特征。為此引入了進程(Process)的概念,以便更好地描述和控制程序的
    發表于 01-15 06:39

    深入剖析ARM64異常處理:開發者必須掌握的底層核心邏輯

    與軟件的協同操作邏輯,都是開發者必備的核心能力。今天我們就深度拆解ARM64異常處理機制,同時聊聊 開發者為何必須關注這一技術點 。 一、異常發生后,CPU的自動操作細節 當ARM64處理器檢測到異常(如中斷、指令錯誤、數據訪問
    的頭像 發表于 12-24 07:05 ?1111次閱讀
    深入剖析ARM64異常<b class='flag-5'>處理</b>:開發者<b class='flag-5'>必須</b><b class='flag-5'>掌握</b>的底層核心邏輯

    嵌入式應掌握的幾種能力

    基本掌握嵌入式處理器的基礎知識。嵌入式處理器種類很多:MCU(微控制)、MPU(微處理器)、DSP(數字信號
    發表于 12-08 06:05

    關于協處理器自定義指令的實現

    ‘b1111011 ——— 7’h7b 隨后的6表示指令的14到12位,即funct3, 協處理器的rtl代碼中可見這個定義,這里的110的順序對應順序為rd,rs1,rs2,使用寄存的話就把對應
    發表于 10-31 06:36

    利用Verdi調試協處理器的實現步驟

    本次給大家介紹的是利用Verdi調試協處理器的實現步驟。 有時為了觀察協處理器運行情況,需要查看協處理器接口的信號波形,此時可以用Verdi來查看主處理器發給協
    發表于 10-30 08:26

    基于E203 NICE協處理器擴展指令

    國一的協處理器應用 (1) 概念 領域特定架構(Domain SpecificArchitecture,DSA),使用特定的硬件做特定的事情[18],也就是說,將主處理器和協
    發表于 10-21 14:35

    基于E203 NICE協處理器擴展指令2.0

    應用 (1) 概念 領域特定架構(Domain Specific Architecture,DSA),使用特定的硬件做特定的事情[18],也就是說,將主處理器和協處理器加速
    發表于 10-21 10:39

    龍芯處理器支持WINDOWS嗎?

    龍芯處理器目前不支持原生運行Windows操作系統,主要原因如下: 架構差異 龍芯架構:龍芯早期基于MIPS架構,后續轉向自主研發的LoongArch指令集(與x86/ARM不兼容
    發表于 06-05 14:24

    需要遵循哪些步驟才能在協同處理器模式配置 FX2/FX3?

    FX2/FX3 替換 CY7C67300。 我們需要遵循哪些步驟才能在協同處理器模式配置 FX2/FX3? 1。是否需要單獨編程 FX2/FX3 才能與我們的主控制接口? 2。G
    發表于 05-26 07:58

    技術分享 | 如何在2k0300(LoongArch架構處理器上跑通qt開發流程

    技術分享 | 如何在2k0300開發板(LoongArch架構處理器上跑通qt開發流程
    的頭像 發表于 05-20 11:05 ?904次閱讀
    技術分享 | 如何在2k0300(LoongArch<b class='flag-5'>架構</b>)<b class='flag-5'>處理器</b>上跑通qt開發流程