Rust Cheet Sheet(アトミック操作)

Written on 2024-12-19

Rustのアトミック操作についての自分のためのまとめ。 『詳解 Rustアトミック操作とロック』の読書メモ。

Box

値をヒープに確保する。

  • Box::leak():確保された領域を開放しない。'staticと互換性があるプログラム終了までのライフタイムを持つ。

Rc

参照カウント。シングルスレッドでしか使えない。Arcより軽い。

  • clone()しても中身は複製されない。

Arc

「アトミック」参照カウント。他のスレッドに移動できる。CPUがアトミック操作をサポートしている必要がある。Rcよりも重い。

Cell

内部可変性を提供する。

  • シングルスレッドしか使えない
  • 値を変更するときは、
    1. .take()で、現在の値を得て、Cellの中身は空に置き換える。
    2. 得られた値を変更して、
    3. .set()で変更された値をCellに書き戻す。

RefCell

参照カウンタを使ったCellCellよりも重い。

  • シングルスレッドしか使えない
  • 他にだれも可変借用していなければ、可変借用できる。
    • 可変借用を使えば、内容を直接変更できる。
  • 普遍借用は複数できる。

UnsafeCell

  • CellMutexの実装に使われる。
  • .get()で生ポインタを取ることができるが、それはunsafe内で扱う必要がある。

Send

トレイト。別のスレッドに送ることができる。

Sync

  • トレイト。別のスレッドと共有することができる。
  • &TSendならばTSync

  • プリミティブ型(u32, bool, strなど)はSendかつSync
  • すべてのフィールドがSendかつSyncな構造体はSendかつSync(トレイトが自動で実装される)。
    • トレイトを自動で実装したくない場合は、PhantomData<T>のTにSend,Syncでない型を含めたメンバーをもたせる。PhantomDataはコンパイラの型推論に使われるだけで、実際のメモリを消費しない。
  • Arc<i32>SendRc<i32>Sendではない。
  • Cell<i32>SendだがSyncではない。
  • 明示的に実装したい場合はunsafe impl Send for X{}, unsafe impl Sync for X{}と実装することができる。Send, Syncしたときの安全性はコンパイラは保証してくれず、プログラマが保証しなければならないのでunsafe

ロック: MutexRwLock

  • Mutexはロックされていない状態とロックされている状態を取る。
    • ロックされているときは排他的なアクセス(読み書き)が可能。
    • lock()は、ロックが取れるまで待ち続ける。
      • lock()はロックが取れたらMutexGuardを返す。Deref, DerefMutトレイトが実装されてるので中のデータに透過的にアクセスできる。
    • try_lock()はロックが取れたらResult<MutexGuard<T>>を返し、取れなかったらErrを返す。
    • ロックがスコープを外れてDropしたときに自動でアンロックされる。
      • スコープが終わらないうちにDropしたいときはdrop()を使う。
      • list.lock().unwrap().push(2);のイデオムは、その場でロックを取得してメンバ関数を実行し、その行でロックをドロップする。
    • get_mut()はロックを取ったうえでミュータブルな参照を返す。
    • into_inner()はMutexを消費する。つまり、所有権を取得して中身を返す。Mutexによる保護は失われ、以後、他のスレッドはlock()することができない。
  • RwLockはロックしたときに、共有参照(&T)と排他参照(&mut T)を返す。
    • read()はリードロック(共有参照)
    • write()はライトロック(排他参照)
    • RefCellのマルチスレッド版

Mutexがドロップするスコープについて注意が必要。

    list.lock().unwrap().push(2);   // ロックはこの行でドロップされる
    if let Some(item) = list.lock().unwrap().pop() {
        process_item(item);
    }                                                   // ロックはこの行まで確保されたまま
    if list.lock().unwrap().pop() == Some(1) {          // ロックはこの行でドロップされる
        do_something;
    }

このように書けば良い。

    let item = list.lock().unwrap().pop();              // ロックはこの行でドロップされる
    if let Some(item) = item {
        process_item(item);
    }

アトミック操作

  • load(), store()操作はCortex-M0+でもサポートされている。
  • fetch_add(), compare_exchange()操作はCortex-M0+ではサポートされていない。
    • Cortex-M3以上ではサポートされている。
  • アトミック変数は内部可変性を持つ。
    • mutでなくても、store()操作が可能。
    • スレッド間で共有可能。'staticにすることもできる。
  • アトミック変数はCopyを実装していない。

メモリ・オーダリング

  • Relaxed: 一つのアトミック変数に対する操作順序が一貫している。複数の変数の場合は順序は保証されない。
  • Release/Acquire: Releaseストアが、Acquireロードに先行する。つまり、Releaseより先の現象はAcqireより後で観測できる。
  • AcqRel: AcquireReleaseの組み合わせ。