Rustのstd::sync::atomic::AtomicI32のメモ。
AtomicI32構造体は、i32をアトミック(原子的)に操作するための構造体。
(JavaのAtomicIntegerに相当)
use std::sync::atomic::{AtomicI32, Ordering};
let n = AtomicI32::new(0); let prev = n.fetch_add(1, Ordering::SeqCst); // 1を加算し、加算前の値を返す println!("prev={}", prev); let now = n.load(Ordering::SeqCst); // 現在の値を返す println!("now ={}", now);
AtomicI32のメソッドにはOrderingを引数に取るものがある。
これはメモリーオーダリングの制御を指定するもので、SeqCstを指定していれば最も確実なようだ。
(JavaのAtomicIntegerはSeqCstに相当するらしい)
列挙子 | 説明 |
---|---|
Relaxed |
メモリーオーダリングの制約が無い。アトミックな操作のみ。 |
Release |
ストア時に使用。書き込み操作の後に他のすべての操作が開始されることを保証する。 |
Acquire |
ロード時に使用。読み込み操作の前に他のすべての操作が完了することを保証する。 |
AcqRel |
AcquireとReleaseの両方の効果を持つ。 |
SeqCst |
Sequential Consistency。最も厳密で、全てのスレッドで同じ順序で操作が行われることを保証する。 |
なお、std::cmpにもOrderingという同名の列挙型があるので注意。
AtomicI32の値を変更するstoreメソッドやfetch_addメソッド等は不変メソッドである。(第1引数が&selfであって&mut selfではない)[2024-10-07]
つまり、AtomicI32をフィールドに保持する構造体があって、その値を変更するメソッドを用意する場合、(フィールドの値を変更するにも関わらず、)そのメソッドは不変メソッドでいい。
struct MyStruct { value: AtomicI32, } impl MyStruct { fn new() -> MyStruct { MyStruct { value: AtomicI32::new(0), } } pub fn add(&self, v: i32) { // addメソッドの中でselfフィールドの値を変更しているにも関わらず、addメソッド自体は不変メソッド(引数の型が&self)である self.value.fetch_add(v, Ordering::SeqCst); } pub fn get(&self) -> i32 { self.value.load(Ordering::SeqCst) } }
AtomicI32のメソッド(抜粋)。
メソッド | 説明 | 例 | JavaのAtomicInteger相当 |
---|---|---|---|
new(v: i32) -> AtomicI32 |
AtomicI32インスタンスを生成する。 | let n = AtomicI32::new(0); |
var n = new AtomicInteger(0); |
get_mut(&mut self)
-> &mut i32 |
可変参照を返す。 | let mut n = AtomicI32::new(0); |
|
into_inner(self)
-> i32 |
値を返す。(所有権も移動する) | let n = n.into_inner(); |
|
load(&self, order:
Ordering) -> i32 |
値を取得する。 | let now = n.load(Ordering::SeqCst); |
int now = n.get(); |
store(&self, val: i32,
order: Ordering) |
値をセットする。 (このメソッドは不変メソッドである) |
n.store(10, Ordering::SeqCst); |
n.set(10); |
swap(&self, val: i32,
order: Ordering) -> i32 |
|||
compare_exchange( |
保持している値がcurrentと等しい場合はnewに置き換えて、「Ok(以前の値)」を返す。 等しくない場合は何もせず「Err(現在の値)」を返す。 |
let n = AtomicI32::new(0); |
int r = n.compareAndExchange(0, 9); |
fetch_add(&self,
val: i32, order: Ordering) -> i32 |
valを加算し、加算前の値を返す。 (このメソッドは不変メソッドである) |
let n = AtomicI32::new(0); |
int r = n.getAndIncrement(); |
int r = n.getAndAdd(1); |
|||
fetch_sub(&self,
val: i32, order: Ordering) -> i32 |
valを減算し、減算前の値を返す。 (このメソッドは不変メソッドである) |
let n = AtomicI32::new(0); |
int r = n.getAndDecrement(); |
int r = n.getAndAdd(-1); |
|||
fetch_and(&self,
val: i32, order: Ordering) -> i32 |
|||
fetch_nand(&self,
val: i32, order: Ordering) -> i32 |
|||
fetch_or(&self,
val: i32, order: Ordering) -> i32 |
|||
fetch_xor(&self,
val: i32, order: Ordering) -> i32 |
|||
fetch_update<F>( |
|||
fetch_max(&self,
val: i32, order: Ordering) -> i32 |
|||
fetch_min(&self,
val: i32, order: Ordering) -> i32 |
構造体のフィールドでAtomicI32を持ってカウントする例。(→カウンターをMutexで実装する例)
フィールドにアクセスするメソッドがマルチスレッドで呼ばれる想定。
use std::{ sync::{ atomic::{AtomicI32, Ordering}, Arc, }, thread, };
struct MyCounter { counter: AtomicI32, } impl MyCounter { fn new() -> MyCounter { MyCounter { counter: AtomicI32::new(0), } } fn increment(&self) { // このメソッドが複数スレッドから呼ばれる self.counter.fetch_add(1, Ordering::SeqCst); } fn get(&self) -> i32 { self.counter.load(Ordering::SeqCst) } }
fn main() { let counter = Arc::new(MyCounter::new()); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { for _ in 0..1000 { counter.increment(); } }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!("{}", counter.get()); }
この例のmain関数では、スレッドを10個起こし、各スレッドでは1000回Mycounterのincrementメソッドを呼んでいる。