مقدمه

در درس قبل، ما یک وب سرور چند-ریسمانی کارآمد ساختیم. اما این سرور هنوز یک مشکل دارد: وقتی ما برنامه را با Ctrl-C متوقف می‌کنیم، ThreadPool و ترِدهای آن به صورت ناگهانی از بین می‌روند. این یعنی اگر ترِدی در حال پردازش یک درخواست باشد، کار آن نیمه‌کاره رها می‌شود. ما به راهی نیاز داریم تا به ThreadPool خود بگوییم که دیگر کار جدیدی را نپذیرد و منتظر بماند تا تمام کارهای در حال انجام به پایان برسند. به این فرآیند «خاموش کردن کنترل‌شده» یا Graceful Shutdown می‌گویند.

ارسال سیگنال خاتمه به ترِدها

در حال حاضر، ترِدهای Worker ما در یک حلقه بی‌نهایت (loop) منتظر دریافت کار از کانال هستند. این حلقه هرگز به پایان نمی‌رسد. ما باید راهی برای شکستن این حلقه پیدا کنیم. یک راه حل، تغییر منطق کانال است. به جای ارسال مستقیم Job، می‌توانیم یک enum تعریف کنیم که یا حاوی یک کار جدید باشد یا یک سیگنال برای خاتمه دادن به ترِد.

Copy Icon src/lib.rs

enum Message {
    NewJob(Job),
    Terminate,
}
// ... and then change the channel to send `Message` enum
let (sender, receiver) = mpsc::channel<Message>();

// In Worker::new
loop {
    let message = receiver.lock().unwrap().recv().unwrap();
    match message {
        Message::NewJob(job) => {
            println!("Worker {id} got a job; executing.");
            job();
        }
        Message::Terminate => {
            println!("Worker {id} was told to terminate.");
            break;
        }
    }
}

اکنون Workerها با دریافت پیام Terminate، از حلقه خود خارج شده و به کار خود پایان می‌دهند.

پیاده‌سازی Drop برای ThreadPool

ما می‌خواهیم این فرآیند خاموش کردن به صورت خودکار انجام شود، یعنی زمانی که ThreadPool از حوزه خارج می‌شود. بهترین مکان برای این منطق، پیاده‌سازی Drop Trait برای ThreadPool است.

Copy Icon src/lib.rs
impl Drop for ThreadPool {
    fn drop(&mut self) {
        println!("Sending terminate message to all workers.");

        for _ in &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();
            }
        }
    }
}

در متد drop، ما ابتدا به تعداد Workerهای موجود، پیام Terminate را به کانال ارسال می‌کنیم. سپس، روی تمام Workerها پیمایش کرده و روی JoinHandle آنها join می‌کنیم. این کار تضمین می‌کند که ترِد اصلی منتظر می‌ماند تا تمام Workerها کارهای در حال انجام خود را تمام کرده و از حلقه خود خارج شوند.

به‌روزرسانی نهایی سرور

حالا که ThreadPool ما از Graceful Shutdown پشتیبانی می‌کند، سرور ما بسیار قوی‌تر و قابل اعتمادتر شده است. با Ctrl-C، ترِد اصلی به پایان می‌رسد، ThreadPool از حوزه خارج می‌شود، متد drop آن فراخوانی شده، به تمام Workerها سیگنال خاتمه ارسال می‌شود، و برنامه منتظر می‌ماند تا تمام درخواست‌های در حال پردازش به پایان برسند.

در این درس، با پیاده‌سازی یک ThreadPool با قابلیت Graceful Shutdown، پروژه وب سرور خود را تکمیل کردیم. با این پروژه، ما تقریباً تمام مفاهیم اصلی زبان Rust را به صورت عملی به کار بستیم: از مالکیت و traitها گرفته تا همزمانی و مدیریت خطا.

تبریک می‌گویم! شما با موفقیت دوره آموزش زبان برنامه‌نویسی Rust را به پایان رساندید. شما اکنون یک پایه بسیار محکم در یکی از مدرن‌ترین و قدرتمندترین زبان‌های برنامه‌نویسی سیستمی در اختیار دارید. دنیای Rust بسیار گسترده است و همیشه چیزهای جدیدی برای یادگیری وجود دارد. از اینجا به بعد، می‌توانید به سراغ موضوعات پیشرفته‌تری مانند برنامه‌نویسی async/await، کار با FFI، یا توسعه وب با فریم‌ورک‌هایی مانند Actix Web یا Rocket بروید. موفق باشید!