rust使用Atomic創(chuàng)建全局變量和使用操作方法
Mutex
用起來簡(jiǎn)單,但是無法并發(fā)讀,RwLock
可以并發(fā)讀,但是使用場(chǎng)景較為受限且性能不夠,那么有沒有一種全能性選手呢? 歡迎我們的Atomic
閃亮登場(chǎng)。
從 Rust1.34 版本后,就正式支持原子類型。原子指的是一系列不可被 CPU 上下文交換的機(jī)器指令,這些指令組合在一起就形成了原子操作。在多核 CPU 下,當(dāng)某個(gè) CPU 核心開始運(yùn)行原子操作時(shí),會(huì)先暫停其它 CPU 內(nèi)核對(duì)內(nèi)存的操作,以保證原子操作不會(huì)被其它 CPU 內(nèi)核所干擾。
由于原子操作是通過指令提供的支持,因此它的性能相比鎖和消息傳遞會(huì)好很多。相比較于鎖而言,原子類型不需要開發(fā)者處理加鎖和釋放鎖的問題,同時(shí)支持修改,讀取等操作,還具備較高的并發(fā)性能,幾乎所有的語言都支持原子類型。
可以看出原子類型是無鎖類型,但是無鎖不代表無需等待,因?yàn)樵宇愋蛢?nèi)部使用了CAS
循環(huán),當(dāng)大量的沖突發(fā)生時(shí),該等待還是得等待!但是總歸比鎖要好。
CAS 全稱是 Compare and swap, 它通過一條指令讀取指定的內(nèi)存地址,然后判斷其中的值是否等于給定的前置值,如果相等,則將其修改為新的值
原子類型的一個(gè)常用場(chǎng)景,就是作為全局變量來使用:
use std::sync::atomic::{AtomicI32, Ordering}; use std::thread::{self, JoinHandle}; static R: AtomicI32 = AtomicI32::new(0); fn thread_add() { // 多個(gè)線程修改全局變量 for i in 0..1000 { R.fetch_add(1, Ordering::Relaxed); } } fn main() { // This will POST a body of `foo=bar&baz=quux` let mut init_data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let mut hand_list = Vec::with_capacity(init_data.len()); for i in init_data { hand_list.push(thread::spawn(thread_add)); } for h in hand_list { h.join().unwrap(); } let r_value = R.load(Ordering::Relaxed); println!("全局變量最后的值是: {r_value:?}"); }
并且能夠保證數(shù)據(jù)讀寫不出錯(cuò):
以上代碼啟動(dòng)了數(shù)個(gè)線程,每個(gè)線程都在瘋狂對(duì)全局變量進(jìn)行加 1 操作, 最后將它與線程數(shù) * 加1次數(shù)
進(jìn)行比較,如果發(fā)生了因?yàn)槎鄠€(gè)線程同時(shí)修改導(dǎo)致了臟數(shù)據(jù),那么這兩個(gè)必將不相等。好在,它沒有讓我們失望,不僅快速的完成了任務(wù),而且保證了 100%的并發(fā)安全性。
當(dāng)然以上代碼的功能其實(shí)也可以通過Mutex
來實(shí)現(xiàn),但是后者的強(qiáng)大功能是建立在額外的性能損耗基礎(chǔ)上的,因此性能會(huì)遜色不少:
Atomic實(shí)現(xiàn):673ms Mutex實(shí)現(xiàn): 1136ms
可以看到Atomic
實(shí)現(xiàn)會(huì)比Mutex
快41%,實(shí)際上在復(fù)雜場(chǎng)景下還能更快(甚至達(dá)到 4 倍的性能差距)!
還有一點(diǎn)值得注意: 和Mutex
一樣,Atomic
的值具有內(nèi)部可變性,你無需將其聲明為mut
:
use std::sync::Mutex; use std::sync::atomic::{Ordering, AtomicU64}; struct Counter { count: u64 } fn main() { let n = Mutex::new(Counter { count: 0 }); n.lock().unwrap().count += 1; let n = AtomicU64::new(0); n.fetch_add(0, Ordering::Relaxed); }
這里有一個(gè)奇怪的枚舉成員Ordering::Relaxed
, 看上去很像是排序作用,但是我們并沒有做排序操作啊?實(shí)際上它用于控制原子操作使用的內(nèi)存順序。
內(nèi)存順序
內(nèi)存順序是指 CPU 在訪問內(nèi)存時(shí)的順序,該順序可能受以下因素的影響:
代碼中的先后順序
編譯器優(yōu)化導(dǎo)致在編譯階段發(fā)生改變(內(nèi)存重排序 reordering)
運(yùn)行階段因 CPU 的緩存機(jī)制導(dǎo)致順序被打亂
限定內(nèi)存順序的 5 個(gè)規(guī)則:
在理解了內(nèi)存順序可能存在的改變后,你就可以明白為什么 Rust 提供了Ordering::Relaxed用于限定內(nèi)存順序了,事實(shí)上,該枚舉有 5 個(gè)成員:
Relaxed, 這是最寬松的規(guī)則,它對(duì)編譯器和 CPU 不做任何限制,可以亂序。
Release 釋放,設(shè)定內(nèi)存屏障(Memory barrier),保證它之前的操作永遠(yuǎn)在它之前,但是它后面的操作可能被重排到它前面。
Acquire 獲取, 設(shè)定內(nèi)存屏障,保證在它之后的訪問永遠(yuǎn)在它之后,但是它之前的操作卻有可能被重排到它后面,往往和Release在不同線程中聯(lián)合使用。
AcqRel, 是 Acquire 和 Release 的結(jié)合,同時(shí)擁有它們倆提供的保證。比如你要對(duì)一個(gè) atomic 自增 1,同時(shí)希望該操作之前和之后的讀取或?qū)懭氩僮鞑粫?huì)被重新排序。
SeqCst 順序一致性, SeqCst就像是AcqRel的加強(qiáng)版,它不管原子操作是屬于讀取還是寫入的操作,只要某個(gè)線程有用到SeqCst的原子操作,線程中該SeqCst操作前的數(shù)據(jù)操作絕對(duì)不會(huì)被重新排在該SeqCst操作之后,且該SeqCst操作后的數(shù)據(jù)操作也絕對(duì)不會(huì)被重新排在SeqCst操作前。
這些規(guī)則由于是系統(tǒng)提供的,因此其它語言提供的相應(yīng)規(guī)則也大同小異,大家如果不明白可以看看其它語言的相關(guān)解釋。
Atomic 能替代鎖嗎
那么原子類型既然這么全能,它可以替代鎖嗎?答案是不行:
對(duì)于復(fù)雜的場(chǎng)景下,鎖的使用簡(jiǎn)單粗暴,不容易有坑
std::sync::atomic包中僅提供了數(shù)值類型的原子操作:AtomicBool, AtomicIsize, AtomicUsize, AtomicI8, AtomicU16等,而鎖可以應(yīng)用于各種類型
在有些情況下,必須使用鎖來配合,例如上一章節(jié)中使用Mutex配合Condvar
Atomic 的應(yīng)用場(chǎng)景
事實(shí)上,Atomic雖然對(duì)于用戶不太常用,但是對(duì)于高性能庫的開發(fā)者、標(biāo)準(zhǔn)庫開發(fā)者都非常常用,它是并發(fā)原語的基石,除此之外,還有一些場(chǎng)景適用:
無鎖(lock free)數(shù)據(jù)結(jié)構(gòu)
全局變量,例如全局自增 ID, 在后續(xù)章節(jié)會(huì)介紹
跨線程計(jì)數(shù)器,例如可以用于統(tǒng)計(jì)指標(biāo)
以上列出的只是Atomic適用的部分場(chǎng)景,具體場(chǎng)景需要大家未來根據(jù)自己的需求進(jìn)行權(quán)衡選擇。
到此這篇關(guān)于rust使用Atomic創(chuàng)建全局變量和使用的文章就介紹到這了,更多相關(guān)rust創(chuàng)建全局變量?jī)?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust中實(shí)例化動(dòng)態(tài)對(duì)象的示例詳解
這篇文章主要為大家詳細(xì)介紹了Rust中實(shí)例化動(dòng)態(tài)對(duì)象的多種方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-02-02Rust語言之Prometheus系統(tǒng)監(jiān)控工具包的使用詳解
Prometheus?是一個(gè)開源的系統(tǒng)監(jiān)控和警報(bào)工具包,最初是由SoundCloud構(gòu)建的,隨著時(shí)間的發(fā)展,Prometheus已經(jīng)具有適用于各種使用場(chǎng)景的版本,為了開發(fā)者方便開發(fā),更是有各種語言版本的Prometheus的開發(fā)工具包,本文主要介紹Rust版本的Prometheus開發(fā)工具包2023-10-10Rust調(diào)用C程序的實(shí)現(xiàn)步驟
本文主要介紹了Rust調(diào)用C程序的實(shí)現(xiàn)步驟,包括創(chuàng)建C函數(shù)、編譯C代碼、鏈接Rust和C代碼等步驟,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12Rust 累計(jì)時(shí)間長(zhǎng)度的操作方法
在Rust中,如果你想要記錄累計(jì)時(shí)間,通??梢允褂脴?biāo)準(zhǔn)庫中的std::time::Duration類型,這篇文章主要介紹了Rust如何累計(jì)時(shí)間長(zhǎng)度,需要的朋友可以參考下2024-05-05