優美かどうかはわからない。
成果物
参考
ThreadPool
にDrop
トレイトを実装する
src/lib.rs
impl Drop for ThreadPool { fn drop(&mut self) { for worker in &mut self.workers { println!("Shutting down worker {}", worker.id); worker.thread.join().unwrap(); } } }
$ cargo run ... error[E0507]: cannot move out of borrowed content --> src/lib.rs:53:13 | 53 | worker.thread.join().unwrap(); | ^^^^^^^^^^^^^ cannot move out of borrowed content
「借用したものから所有権を移動できません」と怒られる。join()
はthread
の所有権を奪う。だが、親のworker
は借用されたものなので所有権を奪えず実行できない。
Workerが代わりにOption<thread::JoinHandle<()>>を保持していれば、 Optionに対してtakeメソッドを呼び出し、Some列挙子から値をムーブし、その場所にNone列挙子を残すことができます。言い換えれば、実行中のWorkerにはthreadにSome列挙子があり、Workerを片付けたい時には、 ワーカーが実行するスレッドがないようにSomeをNoneで置き換えるのです。
struct Worker { id: usize, thread: Option<thread::JoinHandle<()>>, }
$ cargo run ... error[E0599]: no method named `join` found for type `std::option::Option<std::thread::JoinHandle<()>>` in the current scope --> src/lib.rs:53:27 | 53 | worker.thread.join().unwrap(); | ^^^^ error[E0308]: mismatched types --> src/lib.rs:72:13 | 72 | thread, | ^^^^^^ | | | expected enum `std::option::Option`, found struct `std::thread::JoinHandle` | help: try using a variant of the expected type: `Some(thread)` | = note: expected type `std::option::Option<std::thread::JoinHandle<()>>` found type `std::thread::JoinHandle<_>`
impl Worker { fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker { // --snip-- Worker { id, thread: Some(thread), }
impl Drop for ThreadPool { fn drop(&mut self) { for worker in &mut self.workers { println!("Shutting down worker {}", worker.id); if let Some(thread) = worker.thread.take() { thread.join().unwrap(); } } } }
Option.take()は実行後にSome
をとりだしたあとNone
をセットする。
スレッドに仕事をリッスンするのを止めるよう通知する
現状、仕事を求めて無限にloop
する。これを修正する。
enum Message { NewJob(Job), Terminate, }
pub struct ThreadPool { workers: Vec<Worker>, sender: mpsc::Sender<Message>, } // --snip-- impl ThreadPool { // --snip-- pub fn execute<F>(&self, f: F) where F: FnOnce() + Send + 'static { let job = Box::new(f); self.sender.send(Message::NewJob(job)).unwrap(); } } // --snip-- impl Worker { fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> Worker { let thread = thread::spawn(move ||{ loop { let message = receiver.lock().unwrap().recv().unwrap(); match message { Message::NewJob(job) => { println!("Worker {} got a job; executing.", id); job.call_box(); }, Message::Terminate => { // ワーカー{}は停止するよう指示された println!("Worker {} was told to terminate.", id); break; }, } } }); Worker { id, thread: Some(thread), } } }
ポイントはWorker.new()
のクロージャ。Message::Terminate
のときloop
からbreak
する。
次に、Drop
でTerminate
メッセージを送信する。Terminate
メッセージ送信とjoin
呼出がそれぞれ別々のループである必要がある。さもなくばデッドロックする。
impl Drop for ThreadPool { fn drop(&mut self) { println!("Sending terminate message to all workers."); for _ in &mut self.workers { self.sender.send(Message::Terminate).unwrap(); } println!("Shutting down all workers."); for worker in &mut self.workers { println!("Shutting down worker {}", worker.id); if let Some(thread) = worker.thread.take() { thread.join().unwrap(); } } } }
終了するか動作確認するため、2回リクエストされたら終了するコードを書いてみる。
src/main.rs
fn main() { let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); let pool = ThreadPool::new(4); for stream in listener.incoming().take(2) { let stream = stream.unwrap(); pool.execute(|| { handle_connection(stream); }); } println!("Shutting down."); }
$ cargo run Compiling hello v0.1.0 (/tmp/work/Rust.HttpServer.Shutdown.20190710090353/src/4/hello) Finished dev [unoptimized + debuginfo] target(s) in 3.96s Running `target/debug/hello` Worker 1 got a job; executing. Shutting down. Sending terminate message to all workers. Worker 0 got a job; executing. Shutting down all workers. Shutting down worker 0 Worker 2 was told to terminate. Worker 3 was told to terminate. Worker 1 was told to terminate. Worker 0 was told to terminate. Shutting down worker 1 Shutting down worker 2 Shutting down worker 3
Iterator.take(n)
は最大でも繰り返し数をn
回にする。
以上。完成。
所感
え、2回目のリクエストがきたら終了するのが「優美なシャットダウン」なの? と思ってしまう。
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13
bash 4.4.12(1)-release
bash 4.4.12(1)-release
- rustc 1.34.2 (6c2484dc3 2019-05-13)
- cargo 1.34.0 (6789d8a0a 2019-04-01)
$ uname -a Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux
前回まで
- Rustを学んでみたい(プログラミング言語)
- Rustの環境構築
- RustでHelloWorld
- Rustの和訳ドキュメント
- Cargoでプロジェクト作成・ビルド・実行
- クレートとは?
- Rustで関数を使ってみる
- Rustでモジュールを使ってみる
- Rustで乱数を生成する(rand)
- Rustで標準入力する(std::io::stdin().read_line())
- RustでMatch判定する(match)
- Rustでprintとread_lineを1行にする方法
- Rustで数当てゲーム
- クレート名にドット.が使えない
- Rustの変数と可変性(let, mut) error[E0384]: cannot assign twice to immutable variable
x
- Rustのimmutable束縛とconst定数の違い
- RustのREPL、evcxrのインストールに失敗した
- Rustでコンパイルするときの変数未使用warningを消す
- Rustの変数(再代入、再宣言(シャドーイング))
- Rustのシャドーイングについて
- イミュータブルについて(副作用)
- Rustの定数(const)
- Rustのデータ型(数値)
- Rustのデータ型(論理)
- Rustのデータ型(文字)
- Rustのデータ型(タプル)
- Rustのデータ型(配列)
- Rustの関数
- Rustのif式
- Rustのくりかえし文(loop)
- Rustのくりかえし文(while)
- Rustのくりかえし文(for)
- Rustの所有権(ムーブ)
- Rustの所有権(関数)
- Rustの所有権(スライス)
- Rustの構造体(定義とインスタンス化)
- Rustの構造体(プログラム例)
- Rustの構造体(メソッド)
- Rustの列挙型(enum)
- Rustの列挙型(enum)
- Rustの列挙型(enum)
- Rustのmatch(制御フロー演算子)
- RustでNULLを扱う(Option, Some, None)
- NULL参照は10億ドルの失敗だった
- Rustの列挙型に独自表示を実装する(E0277 対策 std::fmt::Display 実装)
- RustのIfLet(matchの糖衣構文)
- Rustのプロジェクト構造
- Rustのcargoでライブラリ&テスト(単体、結合)
- Rustのモジュール(mod)
- Rustのモジュール(pub)
- Rustのmod参照方法(
mod 子モジュール名;
,use 要素名;
,extern crate クレート名;
,super
) - Rustのインポートまとめ(Rust2018)
- RustのコレクションVec型
- RustのコレクションString型
- RustのコレクションHashMap型
- Rustのコレクション(練習問題)
- Rustのエラー処理
- Rustのジェネリクス
- Rustのトレイト
- Rustのライフタイム1
- Rustのライフタイム2(構造体の定義)
- Rustのライフタイム3(ライフタイム省略)
- Rustのライフタイム4(impl定義)
- Rustの静的ライフタイム5('static)
- Rustのライフタイム6(ジェネリクス、トレイト境界とともに)
- Rustのテストコードを書く
- Rustのテスト実行
- Rustのテスト体系化
- Rustでコマンドライン引数を受け取る
- Rustのファイル読込
- Rustでリファクタリング(モジュール性とエラー処理の向上)
- Rustでテスト駆動開発
- Rustで環境変数を取得する
- RustでStdErr出力
- Rustのクロージャ
- Rustのイテレータ
- Rustのイテレータ(Minigrep改善)
- Rustのイテレータ(パフォーマンス)
- Rustのイテレータ(Minigrep改善)
- Rustのcargo(ビルドのカスタマイズ)
- Rustのcargo(cargo docでドキュメント生成)
- Rustのエクスポート(pub use)
- Rustのクレートを公開する方法(crates.io)
- Rustのcargoでワークスペースをつくる
- Rustのcargo installでバイナリをインストールする
- Rustのcargoを拡張する方法
- Rustのスマートポインタ
- スマートポインタBox
- Rustのスマートポインタ(Derefトレイト)
- Rustのスマートポインタ(Dropトレイト)
- Rustのスマートポインタ(Rc
) - Rustのスマートポインタ(RefCell
) - Rustのスマートポインタ(Weak
) - Rustのスレッド
- Rustのスレッド(メッセージ送受信)
- Rustのスレッド(Mutex、Arc)
- Rustのスレッド(Send、Syncトレイト)
- Rustのオブジェクト指向
- RustのOOP(トレイトオブジェクト)
- Rustのオブジェクト指向(デザインパターン)
- Rustのパターン
- Rustのパターン(論駁可能性)
- Rustパターン(記法)
- Rustの高度な機能(Unsafe Rust)
- Rustの高度な機能(ライフタイム)
- Rustの高度な機能(トレイト)
- Rustの高度な機能(型)
- Rustの高度な機能(関数、クロージャ)
- Rustのサーバ(シングルスレッド)
- Rustのサーバ(マルチスレッド)