RustのThreadが何をしているのかよくわからなかったため簡単なFizzBuzzで使ってみた。
前提
Rustを少し触ったことがある人
今回つくるFizzBuzzの構成
- メインスレッドでfor文を回す。
- サブスレッド1で15 or 5 or 3で割れる数値かどうかを監視する
- サブスレッド2で任意の数で終了するように監視する。
- 一番最後にコード全体を載せています。
注意
※一行一行詳しく解説しません。(まだ完璧に理解できていないので…)
必要なモジュールをスコープにインポート
use std::sync::{Arc, Mutex};
use std::time::Duration;
use std::thread;
use std::process::exit;
スレッドの解説に入る前に
こちらを定義しておく。
static HowManyCheckNum: i32 = 100;
fn main() {
let count_arc = Arc::new(Mutex::new(0));
let flag_arc = Arc::new(Mutex::new(0));
- HowManyCheckNumはいくつまでFizzBuzzを実行するかの制限を指定している。今回は100まで
- count_arcとflag_arcにArc::new(Mutex::new(0))としているのは簡単に言うと、スレッド間で同一のデータを参照し、編集したいから。これを指定しないと、Rustは所有権が移動してしまい、一つのスレッドでしか参照できなくなってしまう。
メインのスレッド
let count_clone = Arc::clone(&count_arc);
let flag_clone = Arc::clone(&flag_arc);
for _ in 1..999 {
thread::sleep(Duration::from_millis(100));
let mut num = count_clone.lock().unwrap();
let mut flag = flag_clone.lock().unwrap();
if *flag == 1 {
println!("{}", num);
}
*num = *num + 1;
*flag = 1;
}
- Arc::cloneで所有権の複製を行う。Mutexのlock()はロックを獲得できるまでブロックする。ロックされている→所有されているという認識。
- for文の中で参照し、編集している。thread::sleepで0.1秒ごとにfor文を回す。要するにnumの値が1→2になるまでに0.1秒要するということ。
- flagについてはサブスレッド1で解説する。とりあえず、flagに1を入れていることだけ頭に入れておいてくれ。
サブスレッド1
thread::spawn({
let count_clone = Arc::clone(&count_arc);
let flag_clone = Arc::clone(&flag_arc);
move || {
loop {
thread::sleep(Duration::from_millis(10));
let mut num = count_clone.lock().unwrap();
let mut flag = flag_clone.lock().unwrap();
if *num % 15 == 0 && *flag == 1{
println!("FizzBuzz:{}", *num);
*flag = 0;
}
else if *num % 3 == 0 && *flag == 1 {
println!("Fizz:{}", *num);
*flag = 0;
}
else if *num % 5 == 0 && *flag == 1{
println!("Buzz:{}", *num);
*flag = 0;
}
else {
continue;
}
}
}
});
- このサブスレッドはnumの値をthread::sleepを使って0.01秒ごとに監視している。
- ロック獲得したnumはMutexGuardで包まれているためアスタリスク(*)でオブジェクトにアクセスできる。
- よってif文を実行し、FizzBuzzの典型的な処理を行う。
- なぜflagが必要なのかというと、メインスレッドが1から2になるまで0.1秒かかるが、サブスレッドは0.01秒ごとに値を監視しているから。つまりnumの値が1から2になるまでに、サブレッドはnumの値を複数回監視し、処理を行っている。そのため、println!をしたあとにflagに0を入れることでメインスレッドが実行されるまで待機という処理を行っている。ためしにflagなしで実行してみるといい、同じ数字が複数回出力されるはずだ。
サブスレッド2
thread::spawn({
let count_clone = Arc::clone(&count_arc);
move || {
loop {
thread::sleep(Duration::from_millis(50));
let num = count_clone.lock().unwrap();
if *num > HowManyCheckNum {
println!("Check FizzBuzz TO {}", HowManyCheckNum);
exit(0);
}
}
}
});
- これは0.05秒ごとにnumの値を確認する。
- ここはそこまで難しくないと思う。HowManyCheckNumで指定した値を超えたらexit(0)で終了する。
全体のコード
use std::sync::{Arc, Mutex};
use std::time::Duration;
use std::thread;
use std::process::exit;
static HowManyCheckNum: i32 = 100;
fn main() {
let count_arc = Arc::new(Mutex::new(0));
let flag_arc = Arc::new(Mutex::new(0));
thread::spawn({
let count_clone = Arc::clone(&count_arc);
let flag_clone = Arc::clone(&flag_arc);
move || {
loop {
thread::sleep(Duration::from_millis(10));
let mut num = count_clone.lock().unwrap();
let mut flag = flag_clone.lock().unwrap();
if *num % 15 == 0 && *flag == 1{
println!("FizzBuzz:{}", *num);
*flag = 0;
}
else if *num % 3 == 0 && *flag == 1 {
println!("Fizz:{}", *num);
*flag = 0;
}
else if *num % 5 == 0 && *flag == 1{
println!("Buzz:{}", *num);
*flag = 0;
}
else {
continue;
}
}
}
});
thread::spawn({
let count_clone = Arc::clone(&count_arc);
move || {
loop {
thread::sleep(Duration::from_millis(50));
let num = count_clone.lock().unwrap();
if *num > HowManyCheckNum {
println!("Check FizzBuzz TO {}", HowManyCheckNum);
exit(0);
}
}
}
});
let count_clone = Arc::clone(&count_arc);
let flag_clone = Arc::clone(&flag_arc);
for _ in 1..999 {
thread::sleep(Duration::from_millis(100));
let mut num = count_clone.lock().unwrap();
let mut flag = flag_clone.lock().unwrap();
if *flag == 1 {
println!("{}", num);
}
*num = *num + 1;
*flag = 1;
}
}
終わりに
Rustを勉強し始めて、日は浅いので詳しく解説できず申し訳ありません。今動いている処理を別で監視するというイメージでThreadを使って書いてみました。このコードでは、簡単であるが、並行処理を実現していると思います。イメージが持てるのではないでしょうか。もし何か拙い点があれば、教えていただけると幸いです。閲覧ありがとうございました。
参考文献
参考: Rust における Arc,Mutex について
参考: The Rust Programming Language 日本語版