概要
マルチスレッド環境で発生するデッドロックを発生と解決について記載
環境
cargo 1.57.0 (b2e52d7ca 2021-10-21
edition = 2021
デッドロック発生コード例
- value1とvalue2をそれぞれMutexで定義し、thread1とthread2の両方からアクセスするコード
- thread1では、v1をロック
- thread2では、v2をロック
- お互いスコープを抜けないと(今回の場合は、thread::spawnの引数に指定したクロージャのスコープ)ロック解除しない※
use std::sync::{Mutex, Arc};
use std::thread;
use std::time::Duration;
fn main() {
// とりあえず、Mutex::newで一つのスレッドからのみアクセス可能と定義している
// Arcは、複数のスレッド(thread1, thread2)に所有権を渡す必要があるので。
let value1 = Arc::new(Mutex::new(1));
let value2 = Arc::new(Mutex::new(2));
// thread1内にvalue1, value2の所有権を渡すために
// value1, value2へのポインターを複製している&参照数をカウント
let value1_for_thread1 = Arc::clone(&value1);
let value2_for_thread1 = Arc::clone(&value2);
// thread2内にvalue1, value2の所有権を渡すために
// value1, value2へのポインターを複製している&参照数をカウント
let value1_for_thread2 = Arc::clone(&value1);
let value2_for_thread2 = Arc::clone(&value2);
let handel1 = thread::spawn(move || {
println!("thread 1");
let v1 = value1_for_thread1.lock().unwrap();
println!("value1 content is {} in thread 1", *v1);
thread::sleep(Duration::from_secs(3));
println!("waiting for getting value2 in thread 1");
let v2 = value2_for_thread1.lock().unwrap();
println!("value2 content is {} in thread 1", *v2);
});
let handle2 = thread::spawn(move || {
println!("thread 2");
let v2 = value2_for_thread2.lock().unwrap();
println!("value2 content is {} in thread 2", *v2);
thread::sleep(Duration::from_secs(3));
println!("waiting for getting value1 in thread 2");
let v1 = value1_for_thread2.lock().unwrap();
println!("value1 content is {} in thread 2", *v1);
});
for h in vec![handel1, handle2] {
h.join().unwrap();
}
}
※
Mutexが実装しているDropトレイトのdropメソッドの実装は↓のようになっている
ドロップされた時に、ロック解除するように実装されている
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Drop for MutexGuard<'_, T> {
#[inline]
fn drop(&mut self) {
unsafe {
self.lock.poison.done(&self.poison);
self.lock.inner.raw_unlock();
}
}
}
出力結果
thread 2
value2 content is 2 in thread 2
thread 1
value1 content is 1 in thread 1
waiting for getting value1 in thread 2
waiting for getting value2 in thread 1
プロセス切らないと、ずっとプログラムが続いてしまう
解決
明示的にドロップしてあげる
use std::sync::{Mutex, Arc};
use std::thread;
use std::time::Duration;
fn main() {
// とりあえず、Mutex::newで一つのスレッドからのみアクセス可能と定義している
// Arcは、複数のスレッド(thread1, thread2)に所有権を渡す必要があるので。
let value1 = Arc::new(Mutex::new(1));
let value2 = Arc::new(Mutex::new(2));
// thread1内にvalue1, value2の所有権を渡すために
// value1, value2へのポインターを複製している&参照数をカウント
let value1_for_thread1 = Arc::clone(&value1);
let value2_for_thread1 = Arc::clone(&value2);
// thread2内にvalue1, value2の所有権を渡すために
// value1, value2へのポインターを複製している&参照数をカウント
let value1_for_thread2 = Arc::clone(&value1);
let value2_for_thread2 = Arc::clone(&value2);
let handel1 = thread::spawn(move || {
println!("thread 1");
let v1 = value1_for_thread1.lock().unwrap();
println!("value1 content is {} in thread 1", *v1);
drop(v1); // 明示的にv1をdropしてあげると、v1がドロップされるのでvalue1_for_thread1のロックは解除される
thread::sleep(Duration::from_secs(3));
println!("waiting for getting value2 in thread 1");
let v2 = value2_for_thread1.lock().unwrap();
println!("value2 content is {} in thread 1", *v2);
});
let handle2 = thread::spawn(move || {
println!("thread 2");
let v2 = value2_for_thread2.lock().unwrap();
println!("value2 content is {} in thread 2", *v2);
drop(v2); // 明示的にv2をdropしてあげると、value2_for_thread2のロックは解除される
thread::sleep(Duration::from_secs(3));
println!("waiting for getting value1 in thread 2");
let v1 = value1_for_thread2.lock().unwrap();
println!("value1 content is {} in thread 2", *v1);
});
for h in vec![handel1, handle2] {
h.join().unwrap();
}
}
まとめ
基本的にスコープを抜けたら勝手にドロップをしてくれるが、今回のようなケースでは自前でドロップを呼ぶ必要があるケースも存在するのか 。
今回めっちゃ単純なケースを再現したが、複雑なケースでは他の解決方法が必要なのかな?