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

Rust Arc構造体

Ruststd::sync::Arcのメモ。


概要

Arc構造体は、マルチスレッド用のRc構造体
つまり、複数スレッド間でインスタンスの所有権を共有したいときに利用できる。

use std::sync::Arc;

Arcはアトミック(原子的)な操作を行えるRcということらしい。(Rcの参照カウンターの増減は、たぶんスレッドセーフでないのだろう)


ArcはDerefトレイトを実装しているので、Arc型の変数からArc内部のインスタンスのメソッドが呼べる。


Arcが保持できるのは(Rcと同様に)不変オブジェクトのみ。

不変オブジェクトということは、&selfを引数とするメソッドしか呼べない。
&mut selfを引数とするメソッドを呼びたい場合は可変オブジェクトとして保持する必要がある。

可変オブジェクトを保持したいときは、MutexやRwLock等を使う。
(Arc<Mutex<構造体>>のようにする。(RefCellは使えない))


MyCounterという構造体のincrementメソッドを複数スレッドから呼ぶ例。
(→MyCounterをAtomicI32で実装する例Mutexで実装する例

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());
}

まず、MyCounterのインスタンスをArc::new()で保持する。

別スレッドにArcを渡すときは、Arc::clone()でクローン(複製)を作ってそれを渡す。


Arcを外部に受け渡す例

Arcをポインターにして外部に渡し、それを受け取って処理する例。[2024-10-24]

use std::sync::{Arc, Mutex};
// 外部に受け渡す構造体
#[derive(Debug)]
struct MyStruct {
    text: Mutex<String>,
}

impl Drop for MyStruct {
    fn drop(&mut self) {
        println!("drop MyStruct");
    }
}

type MyStructPtr = *const MyStruct;

fn create_my_struct(output_ptr: *mut MyStructPtr) {
    let s = Arc::new(MyStruct {
        text: Mutex::new(String::new()),
    });
    println!("create: {:?}", s);

    let ptr = Arc::into_raw(s);
    unsafe {
        *output_ptr = ptr;
    }
}

Arc::into_raw()でポインターに変換する。
*output_ptrに代入することで、呼び出し元へポインターを返す。


構造体のポインターを受け取って更新する方法その1。

fn update_my_struct(ptr: MyStructPtr, new_text: &str) {
    let s = unsafe { Arc::from_raw(ptr) };
    {
        let mut text = s.text.lock().unwrap();
        *text = new_text.to_string();
    }
    println!("updated: {:?}", s);

    let _ = Arc::into_raw(s);
}

Arc::from_raw()を使ってポインターから構造体を復元し、所有権を獲得する。

使い終わった後に再びArc::into_raw()でポインターに戻しておかないと、スコープから外れたときに所有権が無くなってインスタンスが解放されてしまう。


構造体のポインターを受け取って更新する方法その2。

fn update_my_struct(ptr: MyStructPtr, new_text: &str) {
    if let Some(s) = unsafe { ptr.as_ref() } {
        {
            let mut text = s.text.lock().unwrap();
            *text = new_text.to_string();
        }
        println!("updated: {:?}", s);
    }
}

as_ref()を使って構造体の参照を受け取り、それを使う。


ポインターを破棄する関数も用意する。
「呼び出し元は、最後に必ずこの関数を呼ぶ必要がある」という仕様とする。

fn release_my_struct(ptr: MyStructPtr) {
    let _ = unsafe { Arc::from_raw(ptr) };
}

ポインターから構造体を復元し、スコープから外れることでインスタンスが解放される。


fn main() {
    println!("main start");

    let mut s_ptr: MyStructPtr = std::ptr::null();
    create_my_struct(&mut s_ptr);
    update_my_struct(s_ptr, "up");

    let s = unsafe { Arc::from_raw(s_ptr) }.clone();
    println!("main: {:?}", s);

    release_my_struct(s_ptr);

    println!("main end");
}

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