精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

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

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

3天內不再提示

Rust原子類型和內存排序

jf_wN0SrCdH ? 來源:Rust語言中文社區 ? 作者:Rust語言中文社區 ? 2022-10-31 09:21 ? 次閱讀

1簡介

原子類型在構建無鎖數據結構,跨線程共享數據,線程間同步等多線程并發編程場景中起到至關重要的作用。本文將從Rust提供的原子類型和原子類型的內存排序問題兩方面來介紹。

2Rust原子類型

Rust標準庫提供的原子類型在std::atomic模塊下。Rust提供了AtomicBool, AtomicU8, AtomicU16, AtomicUsize等原子類型。下面我們以AtomicUsize為例介紹原子類型提供的原子操作。基本的load,store, swap原子操作就不過多介紹了。第一個要介紹的就是重要的compare-and-swap(CAS)原子操作,絕大部分無鎖數據結構都或多或少依賴CAS原子操作。Rust提供的compare_and_swap接口如下:

pubfncompare_and_swap(&self,
current:usize,
new:usize,
order:Ordering
)->usize

compare_and_swap接受一個期望的值和一個新的值,這里我們先忽略掉Ordering,后面會詳細介紹,如果變量的值等于期望的值,就將變量值替換成新的值返回成功,否則不做任何修改并返回失敗。compare_and_swap從語義上包含了讀(load)語義和寫(store)語義,先讀出變量的值,和期望值比較,然后寫入內存。原子操作保證了這三個步驟是原子的,在三個步驟之間不會插入其他指令從而導致變量值被修改。從1.50.0開始compare_and_swap被deprecated了,現在需要使用compare_exchange和compare_exchange_weak接口來實現CAS原子操作。


pubfncompare_exchange(
&self,
current:usize,
new:usize,
success:Ordering,
failure:Ordering
)->Result

pubfncompare_exchange_weak(
&self,
current:usize,
new:usize,
success:Ordering,
failure:Ordering
)->Result

compare_exchange比compare_and_swap多了一個Ordering,兩個Ordering分別作為CAS成功的Ordering和失敗的Ordering,后面會有講解,這里先跳過。從源代碼可以看出compare_and_swap就是用compare_exchange實現的,只是compare_and_swap直接用成功情況下的Ordering生成在失敗情況下的Ordering,compare_exchange則有更高的靈活性。


pubfncompare_and_swap(&self,current:$int_type,new:$int_type,order:Ordering)->$int_type{
matchself.compare_exchange(current,
new,
order,
strongest_failure_ordering(order)){
Ok(x)=>x,
Err(x)=>x,
}
}

既然有了compare_exchange,那compare_exchange_weak是做什么用的呢?從官方文檔中可以看出兩個API的唯一區別是compare_exchange_weak允許spuriously fail。那么什么是spuriously fail,在x86平臺上CAS是一條指令完成的,這兩個API在x86平臺上效果沒有區別,但是在arm平臺上,CAS是由兩條指令完成的LL/SC(Load-link/store-conditional),在arm平臺下會發生spuriously fail,來自Wikipedia的解釋

Real implementations of LL/SC do not always succeed even if there are no concurrent updates to the memory location in question. Any exceptional events between the two operations, such as a context switch, another load-link, or even (on many platforms) another load or store operation, will cause the store-conditional to spuriously fail.

簡單的翻譯就是,即使變量的值沒有被更新LL/SC也不是100%成功,在LL/SC之間的異常事件如上下文切換,另外的LL,甚至load或者store都會導致spuriously fail。由于spuriously fail的存在,arm平臺上compare_exchange是compare_exchange_weak加上一個loop實現的。通常我們在使用CAS的時候會把它放在一個loop中,反復重試直到成功,在這種情況下用compare_exchange_weak會獲得一定的性能提升,如果用compare_exchange則會導致循環套循環。那我們該如何選擇compare_change和compare_exchange_weak呢?如果你想在loop中使用CAS,絕大部分情況下使用compare_exchange_weak,除非你在每一次loop中做的事情很多,spuriously fail會導致很大的overhead即使它很少發生,這種情況下使用compare_exchange。再或者你使用loop就是為了避免spuriously fail,那直接使用compare_exchange就可以達到你的目的。

接下來介紹另外一個重要的原子操作fetch-and-add

pubfnfetch_add(&self,val:usize,order:Ordering)->usize

fetch_add也包含了讀寫兩層語義,只是和CAS比起來它不關心變量當前的值,所以它一定成功。fetch_add一般用來做全局計數器。Rust提供了一系列的fetch_and_xxx操作,其中比較有趣的是fetch_update:

pubfnfetch_update(
&self,
set_order:Ordering,
fetch_order:Ordering,
f:F
)->Result
where
F:FnMut(usize)->Option,

它會接受一個函數,并將函數應用到變量上,把生成的值寫回變量中,因為CPU不支持類似的指令,所以其實fetch_update是使用CAS來實現原子性的。源代碼如下,我們可以看這里使用的是compare_exchange_weak,因為它在一個loop中。

pubfnfetch_update(&self,
set_order:Ordering,
fetch_order:Ordering,
mutf:F)->Result<$int_type,?$int_type>
whereF:FnMut($int_type)->Option<$int_type>{
letmutprev=self.load(fetch_order);
whileletSome(next)=f(prev){
matchself.compare_exchange_weak(prev,next,set_order,fetch_order){
x@Ok(_)=>returnx,
Err(next_prev)=>prev=next_prev
}
}
Err(prev)
}

3內存排序

Rust提供了五種內存排序,由弱到強如下,并且內存排序被標記為#[non_exhaustive]表示未來可能會加入新的類型。

#[non_exhaustive]
pubenumOrdering{
Relaxed,
Release,
Acquire,
AcqRel,
SeqCst,
}

Rust的內存排序和C++20保持一致。內存排序作用是通過限制編譯器和CPU的reorder,來使得多個線程看到的內存順序和我們程序所期望的一樣,所以內存排序主要針對的是內存的讀(load)寫(store)操作。編譯器和CPU會在編譯時和運行時來reorder指令來達到提升性能的目的,從而導致程序中代碼順序會和真正執行的順序可能會不一樣,但是reorder的前提是不會影響程序的最終結果,也就是說編譯器和CPU不會reorder相互有依賴的指令從而破壞程序原本的語義。比方說兩條CPU指令,指令A讀取一塊內存,指令B寫一塊內存,如果CPU發現指令A要讀取的內容在cache中沒有命中需要去內存中讀取,需要花額外的CPU cycle,如果指令B要操作的內存已經在cache中命中了,它可以選擇先執后面的指令B。這時候內存排序的作用就體現出來了,內存排序告訴編譯器和CPU哪些指令可以reorder哪些不可以。接下來分別介紹每一種內存排序的意義。

Relaxed:Relaxed Ordering不施加任何限制,CPU和編譯器可以自由reorder,使用了Relaxed Ordering的原子操作只保證原子性。


//Globalvarible staticx:AtomicU32=AtomicU32::new(0); staticy:AtomicU32=AtomicU32::new(0); //Thread1 letr1=y.load(Ordering::Relaxed); x.store(r1,Ordering::Relaxed); //Thread2 letr2=x.load(Ordering::Relaxed);//A y.store(42,Ordering::Relaxed);//B

這段程序是允許產生r1 == r2 == 42。按照正常的程序執行,這個結果看似不合理,但是因為使用了Relaxed Ordering,CPU和編譯器可以自由reorder指令,指令B被reorder到指令A之前,那就會產生r1 == r2 == 42。

Release:Release Ordering是針對寫操作(store)的,一個使用了Release Ordering的寫操作,任何讀和寫操作(不限于對當前原子變量)都不能被reorder到該寫操作之后。并且所有當前線程中在該原子操作之前的所有寫操作(不限于對當前原子變量)都對另一個對同一個原子變量使用Acquire Ordering讀操作的線程可見。Release Ordering寫和Acquire Ordering讀要配對使用從而在兩個或多個線程間建立一種同步關系。具體例子在介紹完Acquire之后一起給出。

Acquire:Acquire Ordering是針對讀操作(load)的,一個使用了Acquire Ordering的讀操作,任何讀和寫操作(不限于對當前原子變量)都不能被reorder到該讀操作之前。并且當前線程可以看到另一個線程對同一個原子變量使用Release Ordering寫操作之前的所有寫操作(不限于對當前原子變量)。

如果前面的例子中load使用Acquire Ordering,store使用Release Ordering,那么reorder就不會發生,r1 == r2 == 42的結果就不會產生。Acquire和Release動作特別像鎖的加鎖和釋放鎖的操作,因此Acquire Ordering和Release Ordering常被用在實現鎖的場景。看下面的例子


//Globalvarible staticDATA:AtomicU32=AtomicU32::new(0); staticFLAG:AtomicBool=AtomicBool::new(false); //Thread1 DATA.store(10,Ordering::Relaxed);//A FLAG.store(true,Ordering::Release);//B //Thread2 while!FLAG.load(Ordering::Acquire){}//C assert!(DATA.load(Ordering::Relaxed)==10);//D

這段程序展示了兩個線程之間同步的一種方式,在線程1中我們在共享內存中寫入數據,然后把FLAG置成true,表明數據寫入完成,在線程2中,我們用一個while循環等待FLAG被置成true,當FLAG被置成true之后,線程2一定會讀到共享內存中的數據。線程1中的Release Ordering寫和線程2中的Acquire Ordering讀建立了順序。當線程2跳出C行的循環表明它可以讀到線程1在B行對FLAG的寫入,按照Release-Acquire Ordering的保證,A行對DATA的寫入不會被reorder到把FLAG置成true之后,并且對DATA的寫入也會對線程2可見。假如這里沒有使用Release-Acquire Ordering,那么線程對DATA的寫入用可能會被reorder到寫FLAG之后,那么線程2就會出現讀到了FLAG但是讀不到DATA的情況。

AcqRel:AcqRel Ordering主要用于read-modify-write類型的操作,比如compare_and_swap,表明了它同時具有Acquire和Release的語義,讀的部分用Acquire Ordering,寫的部分用Release Ordering。

SeqCst:SeqCst是Sequential Consistent的縮寫,是一種最強的Ordering,在對讀使用Acquire Ordering,對寫使用Release Ordering,對read-modify-write使用AcqRel Ordering的基礎上再保證所有線程看到所有使用了SeqCst Ordering的操作是同一個順序,不論操作的是不是同一個變量。

這里包含了兩層意思,第一層意思SeqCst禁止了所有的reorder,針對內存讀(load)寫(store)的reorder一共有四種情況:

loadload reorder:Arquire Ordering保證

loadstore reorder:Arquire Ordering和Release Ordering保證

storestore reorder:Release Ordering保證

storeload reorder:SeqCst Ordering保證

看下面的例子


//Globalvarible staticx:AtomicU32=AtomicU32::new(0); staticy:AtomicU32=AtomicU32::new(0); //Thread1 x.store(1,Ordering::SeqCst);//A letr1=y.load(Ordering::SeqCst);//B //Thread2 y.store(1,Ordering::SeqCst);//C letr2=x.load(Ordering::SeqCst);//D

這里如果不使用SeqCst Ordering就會出現r1 == r2 == 0的結果,原因是每一個線程中的load可以被reorder到store之前,即使我們分別對load和store使用Acquire Ordering和Release Ordering,因為它們都不保證storeload的reorder。

SeqCst Ordering的第二層意思是所有使用了SeqCst Ordering的操作在全局有一個順序,并且所有的線程都看到同樣的順序。比如說全局的順序是A->B->C->D,那么r1 == 0 && r2 == 1,并且第三個線程如果看到了y == 1,那么它一定能看到x == 1,這就是SeqCst Ordering全局唯一順序代表的意義。雖然使用SeqCst Ordering可以保證更嚴格的全局一致性,但是它也會帶來性能損失,使用正確并且合適的內存排序才能獲得最優的性能。

最后解釋一下compare_exchange兩個Ordering的含義,CAS包含1.讀變量,2.和期望值比較,3.寫變量三個步驟,第一個Ordering表示CAS成功下即變量當前的值等于期望值時候,整個操作的Ordering,第二個Ordering表示如果當前比較失敗了情況下,第一步讀操作的Ordering。看一個用CAS實現自旋鎖的例子

//Globallock
staticLOCK:AtomicBool=AtomicBool::new(false);
//Thread1
//Getlock
while(LOCK.compare_exchange_weak(false,true,Ordering::Acquire,Ordering::Relaxed).is_err()){}
do_something();
//Unlock
LOCK.store(false,Ordering::Release);

//Thread2
//Getlock
while(LOCK.compare_exchange_weak(false,true,Ordering::Acquire,Ordering::Relaxed).is_err()){}
do_something();
//Unlock
LOCK.store(false,Ordering::Release);

4總結

本文介紹了Rust提供的原子類型,原子操作以及和原子操作配合使用的內存排序。深入地理解內存排序才能寫出正確并且性能最優的程序。內存排序是一個很深的話題,如有錯誤,歡迎指正,歡迎在評論區留言交流。

審核編輯:湯梓紅

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

    關注

    8

    文章

    3004

    瀏覽量

    73900
  • 標準庫
    +關注

    關注

    0

    文章

    31

    瀏覽量

    7425
  • Rust
    +關注

    關注

    1

    文章

    228

    瀏覽量

    6574

原文標題:Rust原子類型和內存排序

文章出處:【微信號:Rust語言中文社區,微信公眾號:Rust語言中文社區】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    如何在Rust中使用Memcached

    Memcached是一種高性能、分布式的內存對象緩存系統,可用于加速動態Web應用程序。Rust是一種系統級編程語言,具有內存安全、高性能和并發性等特點。Rust語言的Memcache
    的頭像 發表于 09-19 16:30 ?1203次閱讀

    RUST在嵌入式開發中的應用是什么

    Rust是一種編程語言,它使用戶能夠構建可靠、高效的軟件,尤其是用于嵌入式開發的軟件。它的特點是:高性能:Rust具有驚人的速度和高內存利用率。可靠性:在編譯過程中可以消除內存錯誤。生
    發表于 12-24 08:34

    內存與cpu接線按照什么原理排序的呢?

    請問內存與cpu之間的接線按照什么原理排序的,以及cpu如何訪問內存,編寫驅動是不是內存大小不一樣驅動也不一樣?
    發表于 11-28 07:07

    外部排序

    外部排序:1、外部排序:內部排序:信息一次可全部調入內存,信息在內存中的處理時間是主要的時間耗費。外部
    發表于 08-13 14:15 ?0次下載

    支持內存類型

    支持內存類型            支持內存類型是指主板所支持的具體內存
    發表于 04-26 17:54 ?456次閱讀

    主板支持內存類型有哪些?

    主板支持內存類型有哪些? 支持內存類型是指主板所支持的具體內存類型。不同的主板所支持的
    發表于 12-24 14:48 ?1056次閱讀

    內存的傳輸類型有哪些?

    內存的傳輸類型有哪些?         傳輸類型內存所采用的內存
    發表于 12-25 13:37 ?1678次閱讀

    等離子類型

    等離子類型           pccontent>    等離子(簡稱PDP)是采用近幾年來高速發展的等離子平面屏幕技術的新—代
    發表于 12-31 16:22 ?597次閱讀

    pcb接線端子類型有哪些

    pcb接線端子是在印刷線路板中電器鏈接的接線端子,能夠實現電氣連接,那么pcb接線端子類型有哪些呢?下面小編就為大家介紹一下pcb接線端子類型。 pcb接線端子類型一共有 插拔式系列接線端子、 柵欄
    的頭像 發表于 08-22 14:44 ?9882次閱讀

    Rust中GAT和高階類型

    Rust類型系統級別上與Haskell,Scala有許多相似之處。
    的頭像 發表于 11-07 10:21 ?1152次閱讀

    Rust語言助力Android內存安全漏洞大幅減少

    從 Android 12 開始,Google 就在 Android 系統中帶來了 Rust 語言的支持,作為 C/C++ 的替代方案,他們的目標并不是把現有的 C/C++ 代碼都轉換成為 Rust
    發表于 12-06 17:56 ?663次閱讀

    Rust開源社區推出龍架構原生適配版本

    Rust是近年來新興的系統級編程語言,專注于安全性、并發性和性能。Rust擁有豐富的類型系統和所有權模型,可通過在編譯時檢查內存訪問和并發問題,保證
    的頭像 發表于 07-17 16:54 ?478次閱讀
    <b class='flag-5'>Rust</b>開源社區推出龍架構原生適配版本

    如何使用原子類型

    一、何為原子操作 原子操作:顧名思義就是不可分割的操作,該操作只存在未開始和已完成兩種狀態,不存在中間狀態; 原子類型原子庫中定義的數據類型
    的頭像 發表于 11-10 16:21 ?762次閱讀
    如何使用<b class='flag-5'>原子類型</b>

    鴻蒙OS之Rust開發

    Rust是一門靜態強類型語言,具有更安全的內存管理、更好的運行性能、原生支持多線程開發等優勢。
    的頭像 發表于 01-29 17:19 ?904次閱讀

    [鴻蒙]OpenHarmony4.0的Rust開發

    背景 Rust 是一門靜態強類型語言,具有更安全的內存管理、更好的運行性能、原生支持多線程開發等優勢。Rust 官方也使用 Cargo 工具來專門為
    的頭像 發表于 02-26 17:28 ?814次閱讀
    [鴻蒙]OpenHarmony4.0的<b class='flag-5'>Rust</b>開發