Rustで作る命令セットシミュレータの続き、どうも高速化のためには並列処理について勉強する必要が生じてきた気がするので、Rustで並列処理を実現するための方法について勉強している。
実現したいこととしては:
- メインスレッドでエミュレーションが実行され、
MemoryMap
中のメモリが更新される - サブスレッドでメインスレッドを監視し、
MemoryMap
の特定のアドレスがある値に変更されるとそれを検出し当該処理を行う
というものなのだが、これをやっているうちにかなり泥沼に迷い込んでしまった。まず、MemoryMap
をスレッド間で共有することができなかった。MemoryMap
は実体がmut *u8
なのか?これをスレッド間で安全に共有することができない、という訳か。
そこで少しずつ実装を簡単にしていき、どこまで持って行けばスレッド間で情報を共有できるのかいろいろ調査した。最終的にArc
とMutex
を使った情報共有の方法まで戻ってしまった。もはやMemoryMap
は全く関係ない実装になってしまっている。
以下のプログラムは2スレッド間でmem
を共有している。メインのスレッドが0.1秒に一回mem
の値を更新し、サブスレッドがその値を0.01秒に1回監視し、値が100を超えたらその場でプログラムを終了するというものだ。
let mem = Arc::new(Mutex::new(0)); thread::spawn({ let mem_local = Arc::clone(&mem); move || { loop{ thread::sleep(Duration::from_millis(10)); let num = mem_local.lock().unwrap(); if *num > 100 { println!("break loop"); exit(0); } } } }); let mem_local = Arc::clone(&mem); for _num in 0..200 { thread::sleep(Duration::from_millis(100)); let mut num = mem_local.lock().unwrap(); *num = *num + 1; println!("counting up ... {}", *num); }
結局、Rustでスレッド間データ共有をするためには、MemoryMap
以外の方法を使わなければならないという結論に至った。
従って、私の本来の目的を達成するためには大きく方針を転換する必要がある。特定のメモリアドレスへの書き込みをサブスレッドで監視しておき、それを検出したかったのだが、その「特定のメモリアドレス」の場所だけは別のデータ構造、Mutexに置き換えておき、これをサブスレッドから監視しておく必要がある。 QEMUもどきを作っているので、メモリへのアクセスはすべてアセンブリ命令で実現していたのだが、Mutexへの書き込みとなるとさすがにアセンブリで書くわけにはいかない。当該メモリアドレスへのアクセスは常にトラップを起こしてハンドラへ飛び、自力でMutexへ書き込みを行ったうえでサブスレッドがそれを検出する、という方法になりそうだ。 まあ面倒くさいができない話ではない。この方針にもどついて実装を行ってみよう。