全局變量
假如我們正在編寫網絡代碼,我們希望有一個全局變量,一個計數器,每次發送一個資料包都給他遞增一次:
// 伺服器成功處理的資料包數
static PACKETS_SERVED : usize = 0;
這裡可以編譯通過,不過有個問題: PACKETS_SERVED是不可修改的,是以無法修改它。
Rust會盡可能阻止全局可修改狀态。當然使用const聲明的常量是不可修改的。靜态變量預設是不可修改的,是以無法取得某個值的mut引用。static可以聲明為mut,但是再通路它就是不安全的。Rust的這些規則的主要目的都是保證線程安全。(雖然可以通過unsafe代碼塊修改static的值但是不推薦)
static mut NUM: u8 = 0;
fn main() {
unsafe {
NUM += 1;
}
unsafe {
println!("NUM = {:?}", NUM);
}
}
支援遞增PACKETS_SERVED,同時又能保證線程安全的最簡單的方式,就是把它改成一個原子整數:
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
static PACKETS_SERVED: AtomicUsize = AtomicUsize::new(0);
fn main() {
for _ in 0..100 {
PACKETS_SERVED.fetch_add(1, Ordering::SeqCst);
}
println!("Packets served = {:?}", PACKETS_SERVED);
}
這裡使用AtomicUsize::new(0)來初始化一個原子類型的整數。
一些方法
//Adds to the current value, returning the previous value.
fn fetch_add(&self, val: isize, order: Ordering) -> isize;
//Subtracts from the current value, returning the previous value.
fn fetch_sub(&self, val: isize, order: Ordering) -> isize;
// Bitwise "and" with the current value.
fn fetch_and(&self, val: isize, order: Ordering) -> isize;
// Bitwise "or" with the current value.
fn fetch_or(&self, val: isize, order: Ordering) -> isize;
// Bitwise "xor" with the current value.
fn fetch_xor(&self, val: isize, order: Ordering) -> isize
// Loads a value from the atomic integer.
fn load(&self, order: Ordering) -> isize;
//Stores a value into the atomic integer.
fn store(&self, val: isize, order: Ordering);
// Stores a value into the atomic integer if the current value is the same as the current value.
fn compare_and_swap(
&self,
current: isize,
new: isize,
order: Ordering
) -> isize;
//Stores a value into the atomic integer if the current value is the same as the current value.
fn compare_exchange(
&self,
current: isize,
new: isize,
success: Ordering,
failure: Ordering
) -> Result<isize, isize>;
原子全局變量隻能是簡單的整數或者布爾值。要建立其他任何類型的全局變量也需要解決兩個問題,
-
變量必須通過某種方式保證線程安全,因為要不然就不能是全局變量。考慮到安全,靜态變量必須即是Sync, 又是非mut
Rust有針對性的安全共享可變化值的類型:Mutex, RwLock和原子類型。這些類型即使在被聲明為非mut的情況下也是可修改的,這就是他們的目的所在。
- 靜态初始化不能調用函數,這意味着聲明靜态Mutex的顯式方式行不通。
error: expected identifier, found keyword `ref`
--> src/main.rs:13:8
|
13 | static ref HOSTNAME: Mutex<String> = Mutex::new(String::new());
| ^^^ expected identifier, found keyword
可以看到這樣是有錯誤的。
解決方法:
可以使用lazy_static包來解決這個問題。
通過lazy_static宏定義的變量可以使用任何表達式來初始化。這個表達式會在變量第一次被解引用時運作,而值會儲存下來供後續操作使用。
可以像下面這樣使用lazy_static來聲明一個全局Mutex
#[macro_use] extern crate lazy_static;
lazy_static! {
static ref HOSTNAME: Mutex<String> = Mutex::new(String::new());
}
fn main() {
fn i in 0..10 {
HOSRNAME.lock().unwrap().push_str(&format!("{}", i));
}
println!("HOSTNAME = {:?}", HOSTNAME.lock());
}
同樣的技術也可以用于RwLock和AtomicPtr變量。
使用lazy_static!會在每次通路靜态資料時造成微微小的性能損失,因為其實作是用了為一次性初始化而設計的一個低級同步原語std::sync::Once,在背景每次通路懶靜态資料,程式都要執行一次原子加載指令已檢查初始化是否完成。