Thuta Learning
ရှာဖွေရန်
Rust
ProjectsProgrammingbeginner

Project — Concurrent Batch Processing

ဒီခန်းပြီးရင် ဘာတတ်သွားမလဲ

  • Project — Concurrent Batch Processing concept ကို နားလည်ရှင်းပြနိုင်ရန်
  • နမူနာ Rust code ကို ကိုယ်တိုင် run ပြီး output စစ်နိုင်ရန်
  • Tutorial Platform project နှင့် production scenario တွင် မှန်ကန်စွာအသုံးချနိုင်ရန်

နားလည်ထားရမယ့် အချက်

Lesson 21 ရဲ့ CLI content analyzer ကို file ရာနှင့်ချီအထိ scale ချင်ရင် sequential processing (file တစ်ခုပြီးမှ တစ်ခု) ဟာ multi-core CPU ရဲ့ core တွေအားလုံးကို အသုံးမချနိုင်ဘဲ time linearly ကြီးလာမှာဖြစ်ပါတယ်—ဒီ project က Lesson 18 ရဲ့ `std::thread`/`mpsc` channel ကို production-shaped parallel processing pipeline တစ်ခုအဖြစ် direct apply လုပ်တာပါ။ Design decision အဓိကတစ်ခုက file list ကို thread count (ဥပမာ CPU core number, `std::thread::available_parallelism()` ကနေ query လုပ်နိုင်) အလိုက် chunk ခွဲပြီး thread တစ်ခုစီကို chunk တစ်ခုစီ ownership move ပေးလိုက်တာပါ—thread တစ်ခုစီက independent ဖြစ်ပြီး shared mutable state (locks) လိုအပ်မှု လုံးဝမရှိပါဘူး—Lesson 18 ရဲ့ "shared state အစား message passing" philosophy ကို scale ကြီးကြီးထိ extend လုပ်တာပါ။ Thread တစ်ခုစီရဲ့ partial result (file count, total word count) ကို channel ကနေတစ်ဆင့် main thread ဆီ ပို့ပြီး main thread ကသာ aggregate (sum) လုပ်ပါတယ်—ဒါက race condition ဖြစ်နိုင်ချေ ကင်းစင်စေတဲ့ design ပါ။ Sequential version နှင့် concurrent version ကို timing (`std::time::Instant`) ဖြင့် compare လုပ်တဲ့အခါ—CPU core အနည်းအများ, file size, thread spawn overhead (thread ရေအရမ်းများရင် overhead ကိုယ်တိုင်က benefit ကို ကျော်လွန်နိုင်) အလိုက် speedup ရလဒ် ကွာနိုင်ပါတယ်—"concurrency ဟာ always faster" ဆိုတဲ့ assumption ကို data ဖြင့် စစ်ဆေးရမယ့် practical lesson တစ်ခုပါ။

လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်

Lesson 21 ရဲ့ file list ကို `chunks(file_count / num_threads)` ဖြင့် ခွဲပြီး chunk တစ်ခုစီကို `thread::spawn(move || { ... })` ဆီ ownership move ပေးမယ်—thread တစ်ခုစီက chunk ထဲက file များကို sequential read+count လုပ်ပြီး `(files_processed, total_words)` tuple result ကို `tx.send(...)` ဖြင့် main thread ဆီ ပို့မယ်။ Main thread ကတော့ `rx.iter().take(num_threads)` ဖြင့် result အားလုံးကို collect ပြီး `.fold((0, 0), |acc, r| (acc.0 + r.0, acc.1 + r.1))` ဖြင့် final total ကို aggregate မယ်—individual thread ကို join() ခေါ်ဖို့ handle vector ကိုလည်း သိမ်းထားရမယ်။ Sequential baseline version (thread မသုံးဘဲ loop ချည်း) နှင့် concurrent version ကို `Instant::now()`/`elapsed()` ဖြင့် timing နှိုင်းယှဉ်ပြီး file count/thread count အမျိုးမျိုးနှင့် benchmark run ကြည့်မယ်။

အတူတူ စမ်းရေးကြည့်မယ်

rust
use std::sync::mpsc;
use std::thread;
use std::time::Instant;

fn word_count_for(files: &[&str]) -> (usize, usize) {
    let total: usize = files.iter().map(|f| f.split_whitespace().count()).sum();
    (files.len(), total)
}

fn main() {
    let files = vec!["rust ownership basics", "borrow checker rules", "traits and generics", "async runtime tokio"];
    let (tx, rx) = mpsc::channel();
    let start = Instant::now();

    for chunk in files.chunks(2) {
        let chunk: Vec<&str> = chunk.to_vec();
        let tx = tx.clone();
        thread::spawn(move || {
            tx.send(word_count_for(&chunk)).unwrap();
        });
    }
    drop(tx);

    let (files_done, words_done) = rx.iter().fold((0, 0), |acc, r| (acc.0 + r.0, acc.1 + r.1));
    println!("{files_done} files, {words_done} words in {:?}", start.elapsed());
}
You should see
"4 files, 12 words in <duration>" ကို print ထုတ်နိုင်မည်—thread များစွာက result ကို channel ကနေတစ်ဆင့် main thread ဆီ ပေါင်းစည်းပေးခဲ့ကြောင်း သက်သေပြမည်။

၅ မိနစ် စမ်းကြည့်

အပေါ်က concurrent version ကို sequential version (loop ချည်းသာ, thread မသုံး) တစ်ခုနှင့် ရေးနှိုင်းယှဉ်ပါ—file 100 ခု (dummy data) ဖြင့် timing နှစ်မျိုးကို `Instant`/`elapsed()` ဖြင့် measure ပြီး result ကို compare ပါ။

သတိလေးတစ်ချက်

File 4-5 ခုသာရှိတဲ့ small batch အတွက် thread 20 ခု spawn လုပ်ခြင်း—thread spawn overhead (memory, OS context switch) ကနေ actual work ထက် ပိုကြာသွားနိုင်ပါတယ်—chunk size ကို data volume နှင့် ကိုက်ညီအောင် tune ရပါမယ်။

Thread handle (`JoinHandle`) များကို `.join()` ခေါ်ဖို့ သိမ်းမထားခြင်း—main function ဟာ spawned thread များပြီးဆုံးအောင် wait မလုပ်ဘဲ ထွက်သွားနိုင်လို့ result တချို့ ပျောက်နိုင်ပါတယ်—channel-based design မှာတောင် join() ကို consider သင့်ပါတယ်။

The Rust Programming Language — Using Message Passing to Transfer DataRust

ဒီနေရာမှာ လူအများမှားတတ်တယ်

  • File 4-5 ခုသာရှိတဲ့ small batch အတွက် thread 20 ခု spawn လုပ်ခြင်း—thread spawn overhead (memory, OS context switch) ကနေ actual work ထက် ပိုကြာသွားနိုင်ပါတယ်—chunk size ကို data volume နှင့် ကိုက်ညီအောင် tune ရပါမယ်။
  • Thread handle (`JoinHandle`) များကို `.join()` ခေါ်ဖို့ သိမ်းမထားခြင်း—main function ဟာ spawned thread များပြီးဆုံးအောင် wait မလုပ်ဘဲ ထွက်သွားနိုင်လို့ result တချို့ ပျောက်နိုင်ပါတယ်—channel-based design မှာတောင် join() ကို consider သင့်ပါတယ်။
  • နမူနာ code ကို production system ပေါ် တိုက်ရိုက်မစမ်းဘဲ local/test environment တွင် အရင်အတည်ပြုပါ။

လေ့ကျင့်ခန်း

အပေါ်က concurrent version ကို sequential version (loop ချည်းသာ, thread မသုံး) တစ်ခုနှင့် ရေးနှိုင်းယှဉ်ပါ—file 100 ခု (dummy data) ဖြင့် timing နှစ်မျိုးကို `Instant`/`elapsed()` ဖြင့် measure ပြီး result ကို compare ပါ။

You'll know it worked when: "4 files, 12 words in <duration>" ကို print ထုတ်နိုင်မည်—thread များစွာက result ကို channel ကနေတစ်ဆင့် main thread ဆီ ပေါင်းစည်းပေးခဲ့ကြောင်း သက်သေပြမည်။

Project — Concurrent Batch Processing | Thuta Learning