နားလည်ထားရမယ့် အချက်
command တစ်ခုနောက်မှာ & ထည့်လိုက်ရင် သီးခြား child process တစ်ခုအဖြစ် background ကို လွှတ်လိုက်ပြီး script ကို ချက်ချင်း ထိန်းချုပ်မှု ပြန်ပေးလိုက်တယ်၊ ဒါက ရှေ့က command တွေ မပြီးသေးခင် နောက်ထပ် command တွေကို script ဆက်လွှတ်နိုင်စေတယ် — process 1 &, process 2 &, process 3 & ဆိုတဲ့ call သုံးခုက အလှည့်ကျ တစ်ခုပြီးမှ တစ်ခု စတင်တာ မဟုတ်ဘဲ သုံးခုလုံးကို တစ်ပြိုင်နက် နီးပါးစတင်လိုက်တာပါ။ argument မပါဘဲ ခေါ်တဲ့ wait ဟာ script က launch လုပ်ခဲ့တဲ့ background job အားလုံး ပြီးတဲ့အထိ ရပ်ထားပေးတယ်၊ job တစ်ခုတည်းကိုပဲ synchronize လုပ်ချင်ရင် wait "$!" (နောက်ဆုံး background ပို့လိုက်တဲ့ process ရဲ့ PID) နဲ့လည်း ရွေးချယ်ပြီး wait လုပ်လို့ရတယ်။ ဒီ pattern ဟာ တကယ်တမ်း independent ဖြစ်တဲ့ task အနည်းငယ်အတွက် ကောင်းပေမယ့် input ရာနဲ့ ထောင်နဲ့ချီရင် scale မကောင်းဘူး — အဲဒီလောက် & launch နဲ့ wait တစ်ခု လိုအပ်မယ်၊ တစ်ပြိုင်နက် ဘယ်နှစ်ခုအထိ run နိုင်မလဲဆိုတာ ကန့်သတ်ဖို့ လွယ်လွယ်နည်းလမ်း မရှိဘူး။ အဲဒီနေရာမှာ xargs -P N ဝင်လာတယ် — input stream တစ်ခုကို (default အနေနဲ့ line တစ်ခုချင်း) ဖတ်ပြီး command တစ်ခုကို N ခုအထိ တစ်ပြိုင်နက် run ပေးတယ်၊ worker တစ်ခု အလွတ်ရလိုက်တာနဲ့ invocation အသစ်ကို input အားလုံး process ပြီးတဲ့အထိ ဆက်စတင်ပေးတယ်။ speed ရလဒ်ကတော့ တကယ်ပါပဲ — serial run ရင် N ဆ ကြာမယ့် work ကို N ပုံ 1 ပုံလောက်နဲ့ ပြီးနိုင်တယ် — ဒါပေမယ့် စျေးလည်း တကယ်ရှိတယ်- worker အများကြီးက terminal ပေါ် တစ်ပြိုင်နက် ရေးနေတဲ့အတွက် output တွေ line တစ်ကြောင်းချင်းစီ order မမှန်ဘဲ ရောနှောသွားနိုင်ပြီး input 1 အတွက် job ဟာ input 2 ရဲ့ job မစခင် တကယ် ပြီးမယ်၊ ဒါမှမဟုတ် စတောင်မှစမယ်ဆိုတဲ့ အာမခံချက် ဆုံးရှုံးသွားတယ်။ parallel worker တွေကနေ log ထုတ်တာဟာ job ID, input ရဲ့ ကိုယ်ပိုင်နာမည်လို context လုံလောက်စွာ တံဆိပ်ကပ်ထားသင့်တယ်၊ N ကိုယ်တိုင်လည်း တမင် tune လုပ်ထားထိုက်တယ် — CPU core အရေအတွက် ဒါမှမဟုတ် remote API ရဲ့ rate limit ထက် အလွန်များနေရင် အရှိန်မြှင့်ပေးနေတာ မဟုတ်တော့ဘဲ contention ဖြစ်စေနေတာ ဖြစ်လိမ့်မယ်။
လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်
image file ထောင်ချီကို CDN ပေါ် upload မတင်ခင် resize လုပ်ဖို့ လိုတယ်ဆိုပါစို့။ for loop နဲ့ တစ်ခုချင်း resize လုပ်ရင် ဘေးကင်းပေမယ့် နှေးတယ် — conversion တစ်ခုစီ ဝက်စက္ကန့်လောက် ကြာမယ်ဆိုရင် file ထောင်ချီကို serial လုပ်ရင် စောင့်ရတဲ့ အချိန်ချည်း မိနစ်ပေါင်း ဆယ်ချက်တောင် ရှိလာနိုင်တယ်။ file list ကို find . -name '*.jpg' | xargs -P 4 -I{} convert {} -resize 50% resized/{} ထဲ pipe ဖြတ်လိုက်ရင် ဒါ ပြောင်းသွားတယ် — xargs က find ရဲ့ output ကနေ filename တစ်ခုချင်းစီကို ဖတ်ပြီး convert command ထဲက {} အစား ထည့်ပေးတယ်၊ ဒါပေမယ့် conversion တစ်ခုပြီးအောင် စောင့်ပြီးမှ နောက်တစ်ခု မစဘဲ worker လေးခုအထိ တစ်ပြိုင်နက် run နေအောင် ထားပေးပြီး worker တစ်ခု ပြီးလိုက်တာနဲ့ အသစ်တစ်ခု ချက်ချင်း launch လုပ်ပေးတယ်။ CPU core လေးခု ဒါမှမဟုတ် ဒီထက်ပိုတဲ့ machine တစ်ခုပေါ်မှာ ဒါက wall-clock time ကို serial version ရဲ့ လေးပုံတစ်ပုံလောက်အထိ လျှော့ချပေးနိုင်တယ်၊ ဘာလို့ဆိုတော့ image resize ဟာ CPU-bound work ဖြစ်ပြီး core များများပေါ်မှာ တစ်ပြိုင်နက် run တာကနေ အမှန်တကယ် အကျိုးရှိလို့ပါ။ လက်ခံရမယ့် trade-off ကတော့ worker လေးခုရဲ့ "processing {}" log line တွေဟာ find က file listing ထုတ်ပေးတဲ့ order အတိုင်း မဟုတ်ဘဲ ၎င်းတို့ရဲ့ conversion ဘယ်အချိန် ပြီးလဲဆိုတာအလိုက် terminal ပေါ်မှာ ပေါ်လာမယ်ဆိုတာပါပဲ — ဒါကြောင့် ဘယ် file တွေ process ဖြစ်ပြီးလဲဆိုတာကို resized/ directory ထဲက တကယ့် content ကို နောက်မှ စစ်ဆေးမယ့်အစား log order ကို အားကိုးတာဟာ ဒီအခြေအနေမှာ အမှားတစ်ခု ဖြစ်လိမ့်မယ်။
အတူတူ စမ်းရေးကြည့်မယ်
#!/bin/bash
set -euo pipefail
process() {
local id=$1
echo "worker $id: done"
}
echo "--- running jobs in the background with & and waiting with wait ---"
process 1 &
process 2 &
process 3 &
wait
echo "all background jobs finished"
echo "--- xargs -P runs a command against many inputs in parallel ---"
printf "a\nb\nc\n" | xargs -I{} -P 2 echo "processing {}" | sort
အထက်ပါ script ကို run လိုက်ရင် ဒီအတိုင်း output ထွက်ပါမယ်:
--- running jobs in the background with & and waiting with wait ---
worker 1: done
worker 2: done
worker 3: done
all background jobs finished
--- xargs -P runs a command against many inputs in parallel ---
processing a
processing b
processing c၅ မိနစ် စမ်းကြည့်
xargs ဥပမာကို ပြင်ပြီး input သုံးခုအစား ငါးခုကို -P 3 worker တွေနဲ့ process လုပ်ကြည့်ပါ၊ line တွေ order မတည့်ဘဲ ရောက်လာလည်း ခွဲခြားနိုင်ဖို့ echo လုပ်တဲ့ line တစ်ခုစီထဲ input ရဲ့ value ကို ထည့်ပါ။
သတိလေးတစ်ချက်
& နဲ့ background job တွေ လွှတ်ပေမယ့် wait ကို မေ့ကျန်ခြင်း — job တွေ run နေဆဲမှာ script ထွက်သွားတတ် (ဒါမှမဟုတ် ဆက်သွားတတ်) ပြီး အလုပ်တွေ ဒါမှမဟုတ် output တွေ တိတ်ဆိတ်စွာ ပျောက်ကွယ်သွားနိုင်တယ်
parallel job တွေဟာ စတင်ခဲ့တဲ့ order အတိုင်းပဲ ပြီးမယ်လို့ ယူဆခြင်း — job 1 ရဲ့ output က job 2 ထက် ရှေ့ ပေါ်မယ်ဆိုတာကို အားကိုးထားတဲ့ code (ဒါမှမဟုတ် log ဖတ်ခြင်း) ဟာ & နဲ့ xargs -P နှစ်ခုစလုံးမှာ မသေချာနိုင်ဘူး
xargs(1) — Linux manual page — Bash / Shell Scripting