静的変数の特性と安全でないアクセスの必要性
Rustでは、可変な静的変数へのアクセスは安全でないコードブロック内で行う必要があります。これは、静的変数がグローバルにアクセス可能であり、マルチスレッド環境での競合状態を引き起こす可能性があるためです。
定数と静的変数の違い
- 静的変数は固定のメモリアドレスを持ち、常に同じアドレスを通じてアクセスされます
- 定数は使用箇所でインライン展開され、データのコピーが発生する可能性があります
- 可変な静的変数は変更可能ですが、そのアクセスには unsafe ブロックが必要です
マルチスレッド環境での静的変数操作
マルチスレッドで静的変数を変更する場合、相互排除メカニズムが必要です。Rustでは主に以下の2つのアプローチが利用できます:
- LazyLock - 遅延初期化に適し、初期化ロジックが固定で失敗しない場合
- OnceLock - 明示的な初期化制御が必要で、安定版Rustで使用可能
実装例:単一スレッドでの静的変数操作
static mut COUNTER_VALUE: u32 = 0;
fn increment_counter(amount: u32) {
unsafe {
COUNTER_VALUE += amount;
}
}
fn demonstrate_single_thread_access() {
unsafe {
println!("初期値: {}", COUNTER_VALUE);
}
increment_counter(5);
unsafe {
println!("加算後: {}", COUNTER_VALUE);
}
}
実装例:マルチスレッド環境でのLazyLock使用
use std::sync::{LazyLock, Mutex};
use std::thread;
static mut SHARED_COUNTER: LazyLock<Mutex<u32>> = LazyLock::new(|| Mutex::new(0));
static mut EXECUTION_FLAG: u32 = 0;
fn parallel_increment(thread_count: u32) {
let mut thread_handles = Vec::new();
for _ in 0..thread_count {
let handle = thread::spawn(|| {
while unsafe { EXECUTION_FLAG } == 0 {
thread::sleep(std::time::Duration::from_millis(50));
}
let mut guard = unsafe { SHARED_COUNTER.lock().unwrap() };
*guard += 1;
println!("スレッド {:?} がカウンターをインクリメント", thread::current().id());
});
thread_handles.push(handle);
}
unsafe { EXECUTION_FLAG = 1; }
}
実装例:OnceLockを使用した安全な初期化
use std::sync::{OnceLock, Arc, Mutex};
static AGE_STORAGE: OnceLock<Arc<Mutex<u32>>> = OnceLock::new();
fn initialize_age_storage() {
AGE_STORAGE.get_or_init(|| Arc::new(Mutex::new(25)));
}
fn access_shared_age() {
let age_guard = AGE_STORAGE.get().unwrap().lock().unwrap();
println!("現在の年齢: {}", *age_guard);
}
重要な注意点
- LazyLockは nightly Rust でのみ利用可能
- OnceLockは安定版Rustで使用可能で、明示的な初期化が可能
- マルチスレッド環境では適切な同期メカニズムが必須
- 静的変数の変更は可能な限り避け、不変な設計を優先すべき