S-JIS[2024-10-05/2024-10-07] 変更履歴

Rust AtomicI32構造体

Ruststd::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に相当するらしい)

Ordering列挙型
列挙子 説明
Relaxed メモリーオーダリングの制約が無い。アトミックな操作のみ。
Release ストア時に使用。書き込み操作の後に他のすべての操作が開始されることを保証する。
Acquire ロード時に使用。読み込み操作の前に他のすべての操作が完了することを保証する。
AcqRel AcquireとReleaseの両方の効果を持つ。
SeqCst Sequential Consistency。最も厳密で、全てのスレッドで同じ順序で操作が行われることを保証する。

なお、std::cmpにもOrderingという同名の列挙型があるので注意。


AtomicI32の変更処理は不変

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のメソッド

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);
*n.get_mut() = 10;
 
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(
  &self,
  current: i32,
  new: i32,
  success: Ordering,
  failure: Ordering,
) -> Result<i32, i32>
保持している値がcurrentと等しい場合はnewに置き換えて、「Ok(以前の値)」を返す。
等しくない場合は何もせず「Err(現在の値)」を返す。
let n = AtomicI32::new(0);
let r = n.compare_exchange(0, 9, Ordering::SeqCst, Ordering::SeqCst);
assert_eq!(Ok(0), r);
assert_eq!(9, n.load(Ordering::SeqCst));

let n = AtomicI32::new(1);
let r = n.compare_exchange(0, 9, Ordering::SeqCst, Ordering::SeqCst);
assert_eq!(Err(1), r);
assert_eq!(1, n.load(Ordering::SeqCst));
int r = n.compareAndExchange(0, 9);
fetch_add(&self, val: i32, order: Ordering) -> i32 valを加算し、加算前の値を返す。
(このメソッドは不変メソッドである)
let n = AtomicI32::new(0);
let prev = n.fetch_add(1, Ordering::SeqCst);
assert_eq!(0, prev);
assert_eq!(1, n.load(Ordering::SeqCst));
int r = n.getAndIncrement();
int r = n.getAndAdd(1);
fetch_sub(&self, val: i32, order: Ordering) -> i32 valを減算し、減算前の値を返す。
(このメソッドは不変メソッドである)
let n = AtomicI32::new(0);
let prev = n.fetch_sub(1, Ordering::SeqCst);
assert_eq!(0, prev);
assert_eq!(-1, n.load(Ordering::SeqCst));
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>(
  &self,
  set_order: Ordering,
  fetch_order: Ordering,
  f: F,
) -> Result<i32, i32>
F: FnMut(i32) -> Option<i32>
     
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メソッドを呼んでいる。


stdへ戻る / Rustへ戻る / 技術メモへ戻る
メールの送信先:ひしだま