日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

无锁数据结构二-乱序控制(栅栏)

發(fā)布時(shí)間:2024/1/17 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 无锁数据结构二-乱序控制(栅栏) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

內(nèi)存柵欄

由于優(yōu)化會(huì)導(dǎo)致對(duì)代碼的亂序執(zhí)行,在并發(fā)執(zhí)行時(shí)可能帶來問題。因此為了并行代碼的正確執(zhí)行,我們需提示處理器對(duì)代碼優(yōu)化做一些限制。而這些提示就是內(nèi)存柵欄(memory barriers),用來對(duì)內(nèi)存訪問進(jìn)行管理。要詳細(xì)了解內(nèi)存柵欄原理及產(chǎn)生原因,可參考無鎖數(shù)據(jù)結(jié)構(gòu)(基礎(chǔ)篇):內(nèi)存柵障。每種處理器架構(gòu)都能提供一組完整的內(nèi)存柵欄供開發(fā)使用,使用這些,我們能建立不同的內(nèi)存模型。通過內(nèi)存模型,我們能控制并發(fā)的執(zhí)行順序,也即同步。

下面先對(duì)亂序進(jìn)行一些介紹,可參考3,4

亂序

亂序分為編譯器亂序和處理器(cpu)亂序,下面是我對(duì)它們的一些了解。

編譯器亂序

  • 靜態(tài)分析亂序;
  • 可從全局視圖進(jìn)行亂序(跨過程);
  • 處理器亂序

  • 處理器可以獲知程序的運(yùn)行時(shí)動(dòng)態(tài)行為,可以動(dòng)態(tài)對(duì)指令進(jìn)行亂序執(zhí)行。
  • 亂序范圍小,只有有限分析范圍。

  • 由于亂序可在不同層進(jìn)行,柵欄也有多種,可分為編譯器內(nèi)存柵欄(編譯器),內(nèi)存柵欄(cpu)。對(duì)于c++11 來說其定義內(nèi)存模型,std::memory_order對(duì)編譯器和cpu都會(huì)施加影響。下面主要介紹c++11的內(nèi)存模型。

    內(nèi)存模型與執(zhí)行順序

    有如下三種內(nèi)存模型:

  • 序列一致性模型
  • 釋放/獲取語義模型
  • 寬松的內(nèi)存序列化模型
  • 可分成四種執(zhí)行順序

  • 序列一致順序(Sequentially-consistent ordering)
  • 釋放獲取順序(Release-Acquire ordering)
  • 釋放消費(fèi)順序(Release-Consume ordering)
  • 寬松順序(Relaxed ordering)
  • 所有這些內(nèi)存模型定義在一個(gè)C++列表中– std::memory_order,包含以下六個(gè)常量:

    • memory_order_seq_cst 指向序列一致性模型
    • memory_order_acquire, memory_order_release,memory_order_acq_rel, memory_order_consume 指向基于獲取/釋放語義的模型
    • memory_order_relaxed 指向?qū)捤傻膬?nèi)存序列化模型

    其中:

    • memory_order_relaxed 只保證此操作是原子的,不保證任何讀寫內(nèi)存順序。
    • memory_order_consume 對(duì)于當(dāng)前線程依賴于當(dāng)前原子變量A的變量的讀或?qū)懖荒苤嘏诺酱宋恢们?#xff0c;其他線程對(duì)依賴于A的變量的在釋放A前(如store(memory_order_release))的讀寫,對(duì)于當(dāng)前線程都是可見的(釋放/消費(fèi)語義)。在大多數(shù)平臺(tái)上,這只影響到編譯器優(yōu)化> 此語義作為一個(gè)“內(nèi)存的禮物”被引入DECAlpha處理器中。
    • memory_order_acquire 帶此內(nèi)存順序的加載操作,在其影響的內(nèi)存位置進(jìn)行獲得操作:當(dāng)前線程中讀或?qū)懖荒苣鼙恢嘏诺酱思虞d前。其他釋放同一原子變量的線程的所有寫入,為當(dāng)前線程所可見(釋放/獲取語義)。與memory_order_consume區(qū)別:此標(biāo)志影響所有變量讀寫,而consume影響依賴原子變量的讀寫。
    • memory_order_release 帶此內(nèi)存順序的存儲(chǔ)操作進(jìn)行釋放操作:當(dāng)前進(jìn)程中的讀或?qū)懖荒鼙恢嘏诺酱舜鎯?chǔ)后。當(dāng)前線程的所有寫入,可見于獲得(acquire)該同一原子變量的其他線程(釋放/獲取語義),并且對(duì)該原子變量的帶依賴寫入變得對(duì)于其他消費(fèi)同一原子對(duì)象的線程可見(釋放/消費(fèi)語義)。
    • memory_order_acq_rel 帶此內(nèi)存順序的讀-修改-寫操作既是獲得操作又是釋放操作。當(dāng)前線程的讀或?qū)憙?nèi)存不能被重排到此存儲(chǔ)前或后。所有釋放同一原子變量的線程的寫入可見于修改之前,而且修改可見于其他獲得同一原子變量的線程。
    • memory_order_seq_cst 任何帶此內(nèi)存順序的操作既是獲得操作又是釋放操作,加上存在一個(gè)單獨(dú)全序,其中所有線程以同一順序觀測(cè)到所有修改(序列一致)。

    針對(duì)讀(加載),可選memory_order_acquire和 memory_order_consume。針對(duì)寫(存儲(chǔ)),僅能選memory_order_release。Memory_order_acq_rel是唯一可以用來做RMW運(yùn)算,比如compare_exchange, exchange, fetch_xxx。事實(shí)上,因?yàn)镽MW可以并發(fā)執(zhí)行原子讀\寫,原子性RMW原語擁有獲取語義memory_order_acquire, 釋放語義memory_order_release 或者 memory_order_acq_rel.?
    -memory_order_acq_rel – is somehow similar to memory_order_seq_cst, but RMW-operation is located inside the acquire/release-section -memory_order_relaxed – RMW-operation shifting (its load and store parts) upwards/downwards the code (for example, within the acquire/release section, if the operation is located inside such section) doesn’t lead to errors

    c++11可通過下列語句組合實(shí)現(xiàn)內(nèi)存模型

    std::atomic::load syd::atomic::store ... ... std::atomic_thread_fence
    • 1
    • 2
    • 3
    • 4
    • 5

    ?
    一般情況下上圖左右(store(memory_order_release)與atomic_thread_fence(memory_order_release))可以互相替換,但其仍有區(qū)別,Release Fence更加嚴(yán)格,其阻止下方的store穿過它,而Release Operation不行,如下,m_instance的store可在g_dummy.store上:

    Singleton* tmp = new Singleton; g_dummy.store(0, std::memory_order_release); m_instance.store(tmp, std::memory_order_relaxed);
    • 1
    • 2
    • 3

    為簡單,在介紹模型時(shí)只介紹load/store

    下面是我對(duì)各模型的理解

    序列一致順序

    這是一種嚴(yán)格的內(nèi)存模型,它確保處理器按程序本身既定順序執(zhí)行。

    帶標(biāo)簽 memory_order_seq_cst 的原子操作不僅以與釋放/獲得順序相同的方式排序內(nèi)存(在一個(gè)線程中發(fā)生先于存儲(chǔ)的任何結(jié)果都變成做加載的線程中的可見副效應(yīng)),還對(duì)所有擁有此標(biāo)簽的內(nèi)存操作建立一個(gè)單獨(dú)全序。

    釋放獲取順序

    同步僅建立在釋放和獲得同一原子對(duì)象的線程之間。其他線程可能看到與被同步線程的一者或兩者相異的內(nèi)存訪問順序。?
    在強(qiáng)順序系統(tǒng)( x86 、 SPARC TSO 、 IBM 主框架)上,釋放獲得順序?qū)τ诙鄶?shù)操作是自動(dòng)進(jìn)行的。無需為此同步模式添加額外的 CPU 指令,只有某些編譯器優(yōu)化受影響(例如,編譯器被禁止將非原子存儲(chǔ)移到原子存儲(chǔ)-釋放后,或?qū)⒎窃蛹虞d移到原子加載-獲得前)。在弱順序系統(tǒng)( ARM 、 Itanium 、 Power PC )上,必須使用特別的 CPU 加載或內(nèi)存柵欄指令。

    通過下面代碼來說明此模型:

    #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);//memory_order_release 保證data.push_back(42)執(zhí)行順序一定在store前 }void thread_2() {int expected=1;while (!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel)) { //memory_order_acq_rel具有release和acquire,int expected=1;在前,expected = 1;在后//只有當(dāng)thread_1的store執(zhí)行后才會(huì)進(jìn)入循環(huán),此時(shí)能保證data==42,同時(shí)memory_order_acq_relexpected = 1; } }void thread_3() {while (flag.load(std::memory_order_acquire) < 2);//只有在執(zhí)行了flag.compare_exchange_strong后,才跳出循環(huán),此時(shí)已保證data==42assert(data.at(0) == 42); // 決不出錯(cuò) }int main() {std::thread a(thread_1);std::thread b(thread_2);std::thread c(thread_3);a.join(); b.join(); c.join(); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    釋放消費(fèi)順序

    使用memory_order_consume替換memory_order_acquire來提供比memory_order_acquire更弱的控制。

    同步僅在釋放和消費(fèi)同一原子對(duì)象的線程間建立。其他線程能見到與被同步線程的一者或兩者相異的內(nèi)存訪問順序。

    所有異于 DEC Alphi 的主流 CPU 上,依賴順序是自動(dòng)的,無需為此同步模式產(chǎn)生附加的 CPU 指令,只有某些編譯器優(yōu)化收益受影響(例如,編譯器被禁止?fàn)可娴揭蕾囨湹膶?duì)象上的推測(cè)性加載)。

    注意當(dāng)前(2015年2月)沒有產(chǎn)品編譯器跟蹤依賴鏈:消費(fèi)操作被提升成獲得操作。

    釋放消費(fèi)順序的規(guī)范正在修訂中,而且暫時(shí)不鼓勵(lì)使用 memory_order_consume (C++17 起)

    例子如下:

    #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"); // 絕無出錯(cuò): *p2 從 ptr 攜帶依賴assert(data == 42); // 可能也可能不會(huì)出錯(cuò): data 不從 ptr 攜帶依賴,可能在ptr.load前執(zhí)行。 }int main() {std::thread t1(producer);std::thread t2(consumer);t1.join(); t2.join(); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    此順序的典型使用情況,涉及對(duì)很少被寫入的數(shù)據(jù)結(jié)構(gòu)(安排表、配置、安全策略、防火墻規(guī)則等)的共時(shí)讀取,和有指針中介發(fā)布的發(fā)布者-訂閱者情形,即當(dāng)生產(chǎn)者發(fā)布消費(fèi)者能通過其訪問信息的指針之時(shí):無需令生產(chǎn)者寫入內(nèi)存的所有其他內(nèi)容對(duì)消費(fèi)者可見(這在弱順序架構(gòu)上可能是昂貴的操作)。這種場(chǎng)景的一個(gè)例子是 rcu 解引用.

    寬松順序

    提供最弱控制,只保證原子性

    典型使用是計(jì)數(shù)器自增,例如std::shared_ptr 的引用計(jì)數(shù)器,因?yàn)檫@只要求原子性,但不要求順序或同步

    參考文檔

  • std::memory_order
  • 無鎖數(shù)據(jù)結(jié)構(gòu)(基礎(chǔ)篇):內(nèi)存模型[en]
  • 當(dāng)目標(biāo)CPU具有亂序執(zhí)行的能力時(shí),編譯器做指令重排序優(yōu)化的意義有多大?
  • 內(nèi)存柵障
  • Acquire and Release Fences Don’t Work the Way You’d Expect
  • 關(guān)于Singleton中使用DCLP和Memory barrier的一點(diǎn)疑問?
  • Memory Barriers Are Like Source Control Operations
  • 總結(jié)

    以上是生活随笔為你收集整理的无锁数据结构二-乱序控制(栅栏)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。