সরাসরি পর্বে যান
পর্ব ১৭ · ৬৪-এর মধ্যে · মডিউল ৩

FCFS, SJF আর SRTF — Gantt chart দিয়ে হিসাব

FCFS, SJF, SRTF and HRRN — Gantt Charts, the SJF Optimality Proof, Burst Prediction and Full Worked Problems

Taught in Bangla, this lesson solves FCFS, SJF, SRTF and HRRN scheduling with Gantt charts: tie-breaking rules, the convoy effect, an exam-ready exchange-argument proof that SJF minimises average waiting time, preemptive SJF (SRTF), starvation, predicting the next CPU burst with exponential averaging, HRRN's response ratio, and full worked problems with arrival times and idle CPU comparing average TAT, WT and RT, checked with a simulator.

এই পর্বে যা শিখবেন

  • FCFS, SJF, SRTF আর HRRN-এর নিয়ম, সমান হলে কাকে আগে নেওয়া হবে (tie-breaking), আর প্রতিটির Gantt chart আঁকা
  • Convoy effect কেন ঘটে, আর ক্রম বদলালে গড় অপেক্ষা কতটা বদলায়
  • সবাই t = 0-তে এলে SJF কেন গড় waiting time সর্বনিম্ন রাখে — পরীক্ষায় লেখার মতো প্রমাণ
  • Starvation, exponential averaging দিয়ে পরের CPU burst অনুমান, আর α = 0 ও α = 1-এর মানে
  • Arrival time আর CPU-র ফাঁকা সময়সহ পূর্ণ অঙ্ক, চার অ্যালগরিদমের গড় TAT/WT/RT-এর তুলনা — আর একটি simulator দিয়ে নিজের উত্তর যাচাই

১ · শুরুর আগে — মাপকাঠি আর Gantt chart এক নজরে

পর্ব ১৬-এ দেখেছেন, একটি প্রসেসের জীবন কাটে দুই রকম পালায়: CPU-তে টানা হিসাব (CPU burst) আর I/O-র অপেক্ষা। Short-term scheduler-এর কাজ একটাই — ready queue-এ যারা অপেক্ষা করছে, তাদের মধ্যে পরের CPU কে পাবে, তা ঠিক করা। কোন নিয়মে ঠিক করবে, সেটিই scheduling নীতি। এই পর্বে চারটি নীতি: FCFS, SJF, SRTF আর HRRN.

ব্যাংকের একটি শাখায় একটিমাত্র ক্যাশ কাউন্টারের কথা ভাবুন। কেউ শুধু একটি চেক জমা দেবেন (দুই মিনিট), কেউ বিশটি ড্রাফট বানাবেন (আধা ঘণ্টা)। কর্মকর্তা কাকে আগে ডাকবেন — টোকেনের ক্রমে, সবচেয়ে ছোট কাজের মানুষটিকে, নাকি যিনি সবচেয়ে বেশিক্ষণ বসে আছেন তাঁকে? প্রতিটি উত্তরই একটি scheduling নীতি, আর প্রতিটিতে কারও লাভ, কারও ক্ষতি। হিসাব করে সেই লাভ-ক্ষতি মাপাই এই পর্বের কাজ।

মাপার জন্য প্রতিটি প্রসেসের দুটি তথ্য লাগে: arrival time (AT, কখন ready queue-তে এল) আর burst time (BT, কতটুকু CPU-সময় লাগবে)। Scheduling শেষে Gantt chart থেকে পাওয়া যায় completion time (CT). বাকি তিনটি মাপ পর্ব ১৬-এর সংজ্ঞা অনুযায়ীই:

TAT = CT − AT  ·  WT = TAT − BT  ·  RT = প্রথমবার CPU পাওয়ার মুহূর্ত − AT

এখানে ধরে নেওয়া হয়েছে প্রতিটি প্রসেসের একটিই CPU burst আর কোনো I/O নেই, আর context switch-এর খরচ শূন্য — পরীক্ষার প্রায় সব অঙ্কে এটিই নিয়ম (প্রশ্নে অন্য কিছু বলা থাকলে সেটিই মানবেন; switch-এর খরচ ধরলে কী হয়, পর্ব ৯-এ দেখেছেন)। Non-preemptive নীতিতে একজন একবারেই পুরো burst চালায়, তাই সেখানে RT = WT. Preemptive নীতিতে একজন কয়েক টুকরোয় চলতে পারে, তখন RT ছোট হলেও WT বড় হতে পারে।

দুটি শব্দ মনে রাখুন। Non-preemptive নীতিতে একবার CPU পেলে প্রসেসটি নিজে শেষ না হওয়া পর্যন্ত (বা I/O-তে না যাওয়া পর্যন্ত) কেউ তাকে সরায় না — FCFS, SJF, HRRN এমন। Preemptive নীতিতে মাঝপথে থামানো যায় — SRTF এমন, আর পর্ব ১৮-এর Round Robin-ও। আরেকটি জিনিস: CPU যখন কারও কাজ পায় না — ready queue খালি — সেই idle সময় Gantt chart-এ আলাদা খোপে লিখতে হয়। পরীক্ষায় এই ফাঁক বাদ দেওয়া খুব প্রচলিত ভুল।

২ · FCFS — যে আগে আসে, সে আগে পায়

নিয়ম: ready queue একটি সাধারণ FIFO queue. নতুন প্রসেস এলে সারির পেছনে দাঁড়ায়; CPU খালি হলে সারির মাথার জন সেটি পায় আর পুরো burst শেষ করে তবেই ছাড়ে। সমান arrival time হলে প্রশ্নে দেওয়া ক্রম (P1 আগে, তারপর P2…) মানা হয়। বাস্তবায়ন সবচেয়ে সহজ: একটি linked list, যোগ করা আর বের করা দুটোই O(1). বোঝাও সহজ, আর এক অর্থে ন্যায্য — কেউ লাইন ভেঙে আগে যায় না।

কিন্তু “ন্যায্য” মানে “ভালো” নয়। পাঠ্যবইয়ের সবচেয়ে বিখ্যাত উদাহরণটি দেখুন: তিনটি প্রসেস একসাথে (t = 0) এসেছে, burst P1 = ২৪, P2 = ৩, P3 = ৩ ms. প্রথমে P1, P2, P3 ক্রমে, তারপর উল্টো করে P2, P3, P1 ক্রমে চালিয়ে দেখুন।

1order A: P1(24) P2(3) P3(3)2  24-27  P2 runs3  27-30  P3 runs  -> avg WT 174order B: P2(3) P3(3) P1(24)5   3-6   P3 runs6   6-30  P1 runs  -> avg WT 3
অপেক্ষা (WT, ms)
ক্রম
P1
P2
P3
মোট WT
গড় WT
  1. ক্রম A: লম্বা কাজ আগে। P1 সারির মাথায়, তাই সে ০ থেকে ২৪ পর্যন্ত টানা CPU ধরে রাখে। তার নিজের অপেক্ষা ০. কিন্তু P2 আর P3 — যাদের কাজ মাত্র ৩ ms করে — পুরো সময়টা বসে আছে।
  2. ২৪–২৭: P2-এর পালা। P2 এসেছিল t = 0-তে, শুরু করল ২৪-এ, তাই WT = ২৪. তিন মিনিটের চেক জমা দিতে চব্বিশ মিনিট লাইনে।
  3. ২৭–৩০: P3 শেষ। P3-এর WT = ২৭. মোট অপেক্ষা ০ + ২৪ + ২৭ = ৫১, গড় ১৭ ms. গড় turnaround (২৪ + ২৭ + ৩০) ÷ ৩ = ২৭ ms.
  4. ক্রম B: একই তিনটি কাজ, শুধু P1 শেষে। এবার P2 সারির মাথায়, ০–৩ চলে, অপেক্ষা ০. মোট CPU-কাজ সেই ৩০ ms-ই — কিছুই বদলায়নি, শুধু ক্রম।
  5. ৩–৬: P3. P3 অপেক্ষা করল মাত্র ৩ ms. দুটি ছোট কাজই প্রায় সঙ্গে সঙ্গে শেষ।
  6. ৬–৩০: P1 শেষে। P1-এর অপেক্ষা ৬ — তার নিজের ২৪-এর তুলনায় সামান্য। মোট অপেক্ষা ০ + ৩ + ৬ = ৯, গড় ৩ ms — আগের ১৭-এর তুলনায় প্রায় ছয় ভাগের এক ভাগ। গড় turnaround ২৭ থেকে নেমে ১৩. FCFS-এর ফল তাই পুরোপুরি নির্ভর করে কে আগে এসে পড়ল তার ওপর।

এই ঘটনার নাম convoy effect — মহাসড়কে একটি ধীর ট্রাকের পেছনে বাস, কার, মোটরসাইকেলের লম্বা সারি, ওভারটেকের উপায় নেই। আসল সিস্টেমে ক্ষতি আরও বড়, কারণ পুরো যন্ত্রের ব্যবহার খারাপ হয়। একটি CPU-bound প্রসেস CPU ধরে রাখার সময় অনেকগুলো I/O-bound প্রসেস নিজেদের I/O শেষ করে ready queue-তে জমে — ডিস্ক আর নেটওয়ার্ক তখন অলস। তারপর CPU-bound প্রসেসটি I/O-তে গেলে ছোটরা ঝটপট নিজেদের burst সেরে আবার I/O-তে যায় — এবার CPU অলস। দুই পক্ষ পালা করে অলস থাকে, অথচ একসাথে দুটোই ব্যস্ত থাকতে পারত।

FCFS আর interactive সিস্টেম মেলে না। একটি প্রসেস দীর্ঘ হিসাবে বা অসীম লুপে পড়লে বাকিরা অনির্দিষ্টকাল অপেক্ষা করে, অথচ মাউস-কিবোর্ডের ব্যবহারকারী কয়েকশো মিলিসেকেন্ডের দেরিও টের পান। তাই ডেস্কটপ বা ফোনের OS-এ খাঁটি FCFS চলে না; তবে অনেক নীতির ভেতরের অংশ হিসেবে FCFS সবখানে আছে — Round Robin-এর সারি FIFO, আর Linux-এর SCHED_FIFO একই অগ্রাধিকারের মধ্যে FCFS (অংশ ৯).

৩ · SJF — সবচেয়ে ছোট কাজটি আগে

উপরের উদাহরণের শিক্ষা পরিষ্কার: ছোট কাজ আগে গেলে গড় অপেক্ষা কমে। এই ধারণাকে নিয়ম বানালেই Shortest Job First (SJF). সঠিক নামটি আসলে “shortest-next-CPU-burst” — পুরো প্রসেসের দৈর্ঘ্য নয়, তার পরের CPU burst-এর দৈর্ঘ্য দেখা হয়। William Stallings-এর বইয়ে একই নীতির নাম SPN (Shortest Process Next).

SJF-এর নিয়ম (non-preemptive)

CPU খালি হলে, যারা ইতিমধ্যে এসে গেছে (AT ≤ বর্তমান সময়), তাদের মধ্যে যার burst সবচেয়ে ছোট, সে CPU পায় আর পুরো burst শেষ করে। কেউ না এলে CPU idle থাকে পরের arrival পর্যন্ত। Tie-breaking: burst সমান হলে FCFS — যে আগে এসেছে; arrival-ও সমান হলে টেবিলের ক্রম (ছোট সূচকের P আগে)। পরীক্ষার খাতায় এই নিয়মটি এক লাইনে লিখে দিন — পরীক্ষকেরা এটি দেখতে চান।

সবচেয়ে প্রচলিত ভুল হলো এখনো না আসা প্রসেসকে বাছাইয়ে ধরা। SJF ভবিষ্যৎ দেখে না: t = 0-তে শুধু P1 থাকলে P1-ই চলবে, আর non-preemptive বলে পরে ছোট কেউ এলেও তাকে থামানো যাবে না। একটি উদাহরণ (সময় ms-এ):

প্রসেসATBTSJF-এ CTTAT = CT − ATWT = TAT − BT
P107770
P22412106
P341843
P45416117
গড়৩২ ÷ ৪ = ৮.০০১৬ ÷ ৪ = ৪.০০
SJF:  │ P1 0-7 │ P3 7-8 │ P2 8-12 │ P4 12-16 │

ধাপে ধাপে: t = 0-তে শুধু P1 এসেছে, তাই P1 চলে ০–৭. t = ৭-এ তিনজন অপেক্ষায়: P2 (৪), P3 (১), P4 (৪). সবচেয়ে ছোট P3 → ৭–৮. t = ৮-এ P2 আর P4 দুজনেরই burst ৪ — tie. নিয়ম অনুযায়ী আগে এসেছে P2 (AT ২), তাই P2 → ৮–১২, তারপর P4 → ১২–১৬. একই টেবিলে FCFS চালালে (P1, P2, P3, P4 ক্রমে) গড় WT হতো ৪.৭৫ — SJF বাঁচাল প্রতি প্রসেসে গড়ে পৌনে এক ms. পরে simulator দিয়ে দুটোই যাচাই করবেন।

SJF-এর ready queue সাধারণত একটি min-heap (key = burst), যোগ-বের করা O(log n) — বিস্তারিত DSA কোর্সের heap পর্বে. আসল কঠিন অংশ ডেটা স্ট্রাকচার নয়, burst-এর দৈর্ঘ্যটাই আগে জানা যায় না (অংশ ৬).

৪ · প্রমাণ — কেন SJF গড় অপেক্ষা সবচেয়ে কম রাখে

বিশ্ববিদ্যালয়ের পরীক্ষায় প্রায়ই আসে: “প্রমাণ করুন, SJF গড় waiting time সর্বনিম্ন করে।” দাবিটি সব অবস্থায় সত্য নয়, তাই আগে শর্তগুলো স্পষ্ট করে লিখতে হয়।

উপপাদ্য

n টি কাজ, সবাই t = 0-তে উপস্থিত, burst b₁, b₂, …, bₙ জানা, একটি CPU, preemption নেই, switch-এর খরচ নেই। তাহলে burst-এর ছোট থেকে বড় ক্রমে চালালে (SJF) মোট — এবং তাই গড় — waiting time সর্বনিম্ন হয়।

প্রমাণ (exchange argument): যেকোনো একটি ক্রম নিন। ধরুন সেখানে কোথাও পাশাপাশি দুটি কাজ X আর Y আছে — X আগে, Y ঠিক পরে — যেখানে X-এর burst a, Y-এর burst b, আর a > b. এমন জোড়াকে বলি “উল্টো জোড়া”। X শুরু হয় কোনো এক সময় s-এ।

  1. বদলের আগে: X-এর অপেক্ষা s, Y-এর অপেক্ষা s + a. দুজনের যোগফল 2s + a.
  2. X আর Y অদলবদল করলে: Y-এর অপেক্ষা s, X-এর অপেক্ষা s + b. যোগফল 2s + b.
  3. বাকিদের কী হলো? X-Y-এর আগের কাজগুলো একই সময়ে শুরু হয়, কিছুই বদলায় না। পরের কাজগুলোও শুরু হয় s + a + b-তে — দুই ক্রমেই একই। তাই তাদের অপেক্ষাও অপরিবর্তিত।
  4. ফল: মোট অপেক্ষার পরিবর্তন = (2s + b) − (2s + a) = b − a < 0. অর্থাৎ প্রতিটি উল্টো জোড়া সোজা করলে মোট অপেক্ষা ঠিক (a − b) কমে।
  5. উপসংহার: যে ক্রমে একটিও উল্টো জোড়া আছে, তাকে কঠোরভাবে উন্নত করা যায় — তাই সেটি সর্বোত্তম হতে পারে না। পাশাপাশি উল্টো জোড়া একটিও না থাকা মানে ক্রমটি burst অনুযায়ী সাজানো, অর্থাৎ SJF. (সমান burst-এর কাজ অদলবদলে কিছু বদলায় না, তাই সমান burst থাকলে সব SJF-ক্রমই সমান ভালো।) আর প্রতিটি অদলবদল উল্টো জোড়ার সংখ্যা ঠিক একটি কমায় — bubble sort-এর মতো — তাই সসীম ধাপে যেকোনো ক্রম থেকে SJF-এ পৌঁছানো যায়, প্রতি ধাপে মোট অপেক্ষা বাড়ে না। ∎

একই কথা একটি সূত্র দিয়েও দেখা যায়। ক্রমের k-তম কাজটির burst যদি b₍ₖ₎ হয়, তবে সেটি তার পরের (n − k) টি কাজের প্রত্যেকের অপেক্ষায় একবার করে যোগ হয়:

মোট WT = Σₖ (n − k) · b₍ₖ₎ = (n−1)·b₍₁₎ + (n−2)·b₍₂₎ + … + 1·b₍ₙ₋₁₎ + 0·b₍ₙ₎

গুণকগুলো (n − 1, n − 2, …, 0) কমতে থাকে, তাই মোট সবচেয়ে ছোট রাখতে সবচেয়ে বড় গুণকের সাথে সবচেয়ে ছোট burst জোড়া দিতে হয় (rearrangement inequality) — আবার SJF. নিচের diagram-এ চারটি কাজ (burst ৬, ৮, ৭, ৩) নিয়ে উল্টো জোড়া একে একে সোজা করে দেখুন — প্রতি ধাপে মোট অপেক্ষা ঠিক burst-এর পার্থক্যের সমান কমছে।

1start  : 6 8 7 3   (FCFS order P1..P4)2swap 7,3: 6 8 3 73swap 8,3: 6 3 8 74swap 6,3: 3 6 8 75swap 8,7: 3 6 7 86sorted -> no inversion left
ক্রম আর অপেক্ষা
burst-এর ক্রম
প্রত্যেকের WT
মোট WT
এই ধাপে কমল
উল্টো জোড়া বাকি
  1. শুরু: FCFS ক্রম ৬, ৮, ৭, ৩. অপেক্ষা ০, ৬, ১৪ (৬ + ৮), ২১ (৬ + ৮ + ৭) — মোট ৪১, গড় ১০.২৫. উল্টো জোড়া (বড়টি আগে, ছোটটি পরে, যেকোনো দূরত্বে) গুনলে ৪টি: (৬,৩), (৮,৭), (৮,৩), (৭,৩).
  2. পাশাপাশি ৭ আর ৩ বদল। ৩-এর কাজটি এখন তৃতীয় জায়গায়, অপেক্ষা ১৪; ৭-এর কাজটির অপেক্ষা ১৪ + ৩ = ১৭. সামনের দুজনের কিছুই বদলায়নি। মোট ৪১ থেকে ৩৭ — ঠিক ৭ − ৩ = ৪ কম।
  3. এবার ৮ আর ৩ বদল। ৩ এখন দ্বিতীয়, অপেক্ষা ৬; ৮-এর অপেক্ষা ৯. মোট ৩২ — কমল ৮ − ৩ = ৫. লক্ষ করুন, পেছনের ৭-এর কাজটির অপেক্ষা ১৭-ই রইল, কারণ তার আগের মোট burst একই (৬ + ৩ + ৮ = ১৭).
  4. ৬ আর ৩ বদল। সবচেয়ে ছোট কাজটি এখন প্রথমে। মোট ২৯ — কমল ৬ − ৩ = ৩. বাকি একটি উল্টো জোড়া: ৮ আর ৭.
  5. ৮ আর ৭ বদল। মোট ২৮ — কমল ১. এখন ক্রম ৩, ৬, ৭, ৮ — ছোট থেকে বড়, অর্থাৎ SJF.
  6. আর কোনো উল্টো জোড়া নেই। প্রমাণ বলছে, এর চেয়ে ভালো কোনো ক্রম নেই: গড় অপেক্ষা ৭.০০ সর্বনিম্ন। নিচের C প্রোগ্রাম ২৪টি ক্রমই চেষ্টা করে এটি নিশ্চিত করে।

প্রমাণ পড়ে বিশ্বাস হলো; এবার যন্ত্র দিয়ে যাচাই। চারটি কাজের 4! = ২৪টি সম্ভাব্য ক্রম আছে। নিচের C প্রোগ্রাম সবগুলো তৈরি করে (recursion দিয়ে), প্রতিটির গড় অপেক্ষা হিসাব করে, আর সবচেয়ে ভালো ও খারাপটি SJF-এর সাথে মেলায়।

#include <stdio.h>
#include <stdlib.h>
/* All jobs arrive at t=0. Try every order, report the average waiting time. */
#define N 4
static const int burst[N] = {6, 8, 7, 3};          /* P1..P4 */
static int order[N], used[N], count = 0;
static double best = 1e9, worst = 0;

static void try_all(int k) {
    if (k == N) {                                   /* one complete order */
        int clock = 0, total_wait = 0;
        for (int i = 0; i < N; i++) { total_wait += clock; clock += burst[order[i]]; }
        double avg = (double)total_wait / N;
        if (avg < best) best = avg;
        if (avg > worst) worst = avg;
        count++; return;
    }
    for (int j = 0; j < N; j++)
        if (!used[j]) { used[j] = 1; order[k] = j; try_all(k + 1); used[j] = 0; }
}

static int by_burst(const void *a, const void *b) {
    return burst[*(const int *)a] - burst[*(const int *)b];
}

int main(void) {
    try_all(0);
    int sjf[N] = {0, 1, 2, 3}, clock = 0, total = 0;
    qsort(sjf, N, sizeof sjf[0], by_burst);        /* shortest burst first */
    printf("SJF order:");
    for (int i = 0; i < N; i++) { printf(" P%d", sjf[i] + 1); total += clock; clock += burst[sjf[i]]; }
    printf("\norders tried = %d\n", count);
    printf("best avg WT = %.2f, worst avg WT = %.2f, SJF avg WT = %.2f\n", best, worst, (double)total / N);
    return 0;
}

Wandbox-এ (gcc, Linux) চালানো আসল আউটপুট:

SJF order: P4 P1 P3 P2
orders tried = 24
best avg WT = 7.00, worst avg WT = 11.00, SJF avg WT = 7.00

প্রথম লাইন: qsort burst অনুযায়ী সাজিয়ে SJF-ক্রম দিল P4 (৩), P1 (৬), P3 (৭), P2 (৮). দ্বিতীয় লাইন: ২৪টি ক্রমই পরীক্ষা হয়েছে। তৃতীয় লাইন: সর্বনিম্ন গড় ৭.০০ — SJF-এরও ঠিক ৭.০০. সবচেয়ে খারাপ ক্রম (৮, ৭, ৬, ৩) দেয় ১১.০০ — শুধু ক্রমের কারণেই গড় অপেক্ষা প্রায় দেড় গুণ। এটি প্রমাণ নয়, একটি নমুনায় পরীক্ষা; কিন্তু প্রমাণে ভুল থাকলে এমন পরীক্ষাই প্রথমে ধরিয়ে দেয়। burst বদলে আবার চালিয়ে দেখুন।

শর্ত ভাঙলে দাবিও ভাঙে। প্রসেস ভিন্ন সময়ে এলে non-preemptive SJF সর্বোত্তম নাও হতে পারে। উদাহরণ: P1 আসে t = 0-তে (burst ১০), P2 আসে t = ১-এ (burst ১). SJF t = 0-তে শুধু P1 দেখে, তাকে চালায়: অপেক্ষা ০ আর ৯, গড় ৪.৫. অথচ CPU-কে ইচ্ছা করে ১ ms অলস রেখে P2 আগে চালালে অপেক্ষা হয় P2 = ০, P1 = ২, গড় মাত্র ১. ভবিষ্যৎ না জেনে এমন সিদ্ধান্ত নেওয়া যায় না; আর ভবিষ্যৎ জানা থাকলেও arrival-সহ non-preemptive ক্ষেত্রে সর্বোত্তম ক্রম খুঁজে বের করা NP-hard বলে প্রমাণিত। Preemption থাকলে ছবি বদলায়: পরের অংশের SRTF তখন গড় অপেক্ষা সর্বনিম্ন রাখে।

৫ · SRTF — ছোট কাজ এলে চলতি কাজ থামে

অংশ ৩-এর উদাহরণে P1 (burst ৭) t = 0-তে শুরু করল, আর t = ৪-এ এল মাত্র ১ ms-এর P3. Non-preemptive SJF-এ P3-কে ৭ পর্যন্ত বসে থাকতে হয়েছে। যদি P1-কে মাঝপথে থামানো যেত? সেটিই Shortest Remaining Time First (SRTF) — SJF-এর preemptive রূপ। তাত্ত্বিক বইয়ে এর নাম SRPT (shortest remaining processing time).

SRTF-এর নিয়ম

যখনই নতুন কোনো প্রসেস আসে (বা চলতিটি শেষ হয়), scheduler সব উপস্থিত প্রসেসের বাকি সময় (remaining time) তুলনা করে। নতুনটির burst চলতিটির বাকি সময়ের চেয়ে কঠোরভাবে কম হলে চলতিটি থামে, ready queue-তে ফেরে (তার বাকি সময়সহ), আর নতুনটি CPU পায়। Tie-breaking: বাকি সময় সমান হলে যে চলছে সে-ই চলতে থাকে (অকারণ switch নয়); CPU খালি অবস্থায় সমান হলে আগে আসা জন, তারপর টেবিলের ক্রম।

দুটি ঘটনার (আসা বা শেষ হওয়া) মাঝে কারও তুলনা বদলায় না, তাই সিদ্ধান্ত শুধু ঘটনার মুহূর্তে নিলেই চলে; তবু হাতে হিসাবের সময় এক ms করে এগোনো সবচেয়ে নিরাপদ। নিচের diagram অংশ ৩-এর চার প্রসেস (P1 0/7, P2 2/4, P3 4/1, P4 5/4 — AT/BT) নিয়ে প্রথম সাত ms এক এক করে দেখায়, তারপর শেষ দুটি লম্বা টুকরো একবারে, কারণ t = ৫-এর পরে আর কেউ আসে না।

1t=0  P1 arrives (7)       run P12t=1  nothing new          run P13t=2  P2 arrives (4 < 5)   PREEMPT -> P24t=3  nothing new          run P25t=4  P3 arrives (1 < 2)   PREEMPT -> P36t=5  P3 done, P4 arrives   pick P2 (2)7t=6  nothing new          run P28t=7  P2 done              pick P4 (4 < 5)9t=11 P4 done             pick P1 (5)10t=16 all done
ধাপের শেষে
সময়-খণ্ড
CPU-তে
বাকি P1/P2/P3/P4
ready queue-তে
শেষ হলো
  1. t = ০: শুধু P1. P1 এসেছে (burst ৭), আর কেউ নেই — সে CPU পায়। এক ms পর তার বাকি ৬. P1 প্রথমবার CPU পেল t = ০-তে, তাই তার RT = ০.
  2. t = ১: নতুন কেউ নেই। তুলনার কিছু নেই, P1 চলতেই থাকে। বাকি ৫.
  3. t = ২: P2 এল, burst ৪. তুলনা: P2-এর ৪ বনাম P1-এর বাকি ৫. ৪ < ৫, তাই preemption — P1 থেমে ready queue-তে যায় (বাকি ৫ সঙ্গে নিয়ে), P2 CPU পায়। এক ms পর P2-এর বাকি ৩. P2-এর RT = ২ − ২ = ০.
  4. t = ৩: নতুন কেউ নেই। P2 (৩) বনাম অপেক্ষমাণ P1 (৫) — P2-ই ছোট, চলতে থাকে। বাকি ২.
  5. t = ৪: P3 এল, burst ১. ১ < P2-এর বাকি ২ — আবার preemption. P3 চলে ৪–৫ আর শেষ: CT = ৫, TAT = ১, WT = ০. Queue-তে এখন P1 (৫) আর P2 (২).
  6. t = ৫: P3 শেষ, একই মুহূর্তে P4 এল (৪). CPU খালি, প্রার্থী তিনজন: P1 (৫), P2 (২), P4 (৪). সবচেয়ে কম বাকি P2-এর — সে ফিরে আসে। P4-কে অপেক্ষায় যেতে হলো।
  7. t = ৬: নতুন কেউ নেই। P2 শেষ ms-টি চালায়: CT = ৭, TAT = ৭ − ২ = ৫, WT = ৫ − ৪ = ১. সে একবার থেমেছিল (৪–৫), তাই মোট অপেক্ষা মাত্র ১ ms.
  8. t = ৭: বাকি P1 (৫) আর P4 (৪). P4 ছোট, পায় ৭–১১. এর পরে আর কেউ আসবে না, তাই আর কোনো preemption-এর সুযোগ নেই — ৪ ms একটানা চলে। P4: CT ১১, TAT ৬, WT ২, RT = ৭ − ৫ = ২.
  9. t = ১১: শুধু P1 বাকি। P1 তার বাকি ৫ ms চালায়, ১১–১৬. CT ১৬, TAT ১৬, WT = ১৬ − ৭ = ৯. লক্ষ করুন, P1 সবার আগে এসেছিল, সবার আগে শুরুও করেছিল — অথচ শেষ করল সবার পরে।
  10. ফলাফল। WT: ৯, ১, ০, ২ → গড় ৩.০০ (SJF-এ ছিল ৪.০০). TAT: ১৬, ৫, ১, ৬ → গড় ৭.০০ (SJF-এ ৮.০০). RT: ০, ০, ০, ২ → গড় ০.৫০ (SJF-এ ৪.০০). দাম: ৬টি CPU-খণ্ড মানে ৫টি context switch, যার ২টি জোর করে (preemption). SJF-এ switch ছিল ৩টি।

SRTF-এর দুটি বড় সুবিধা: গড় waiting আর turnaround সর্বনিম্ন (সব burst জানা আর switch-এর খরচ শূন্য — এই শর্তে; ১৯৬০-এর দশকের শেষে Linus Schrage এর আনুষ্ঠানিক প্রমাণ দেন), আর ছোট কাজের response time প্রায় শূন্য। প্রমাণের মূল ধারণা অংশ ৪-এর অদলবদলের যুক্তিই, এবার চলার টুকরোগুলোর ওপর।

অসুবিধাও তিনটি: প্রতিটি preemption একটি context switch, যার খরচ বাস্তবে শূন্য নয় (পর্ব ৯); বাকি সময় জানতে মোট burst জানতে হয়; আর লম্বা কাজের প্রতি এটি SJF-এর চেয়েও নির্দয় — শুরু করেও ছোট কেউ এলেই থামতে হয়। উপরের উদাহরণে P1-এর অবস্থা দেখুন।

৬ · Starvation আর পরের burst-এর অনুমান

Starvation — লম্বা কাজ যখন কখনোই পালা পায় না

ধরুন একটি সার্ভারে প্রতি ২ ms-এ একটি ১ ms-এর ছোট অনুরোধ আসে, সঙ্গে একটি ৫০ ms-এর রিপোর্ট তৈরির কাজ। SJF বা SRTF-এ প্রতিটি সিদ্ধান্তে কোনো না কোনো ছোট অনুরোধ হাজির থাকলে রিপোর্টের পালা পিছোতেই থাকে — চলতে প্রস্তুত থেকেও এগোতে না পারা, এরই নাম starvation. Silberschatz-এর বইয়ে একটি জনশ্রুতি আছে: ১৯৭৩ সালে MIT-র IBM 7094 বন্ধ করার সময় ১৯৬৭ সালে জমা দেওয়া একটি নিম্ন-অগ্রাধিকারের job পাওয়া যায়, যেটি তখনো চলেনি — একে নথির বদলে শিক্ষণীয় কাহিনি হিসেবেই নিন।

প্রতিকারের সাধারণ নাম aging: যত বেশি অপেক্ষা, তত বেশি অগ্রাধিকার — অংশ ৭-এর HRRN এই ধারণাকে একটি সূত্রে বেঁধেছে। FCFS-এ starvation হয় না, সারি এগোয়ই। পার্থক্যটি মনে রাখুন: convoy-তে অপেক্ষা দীর্ঘ কিন্তু সীমিত; starvation-এ অপেক্ষার কোনো ঊর্ধ্বসীমা নেই।

Burst আগে থেকে জানা যায় না — তাহলে অনুমান

SJF আর SRTF দুটোই চায় পরের CPU burst-এর দৈর্ঘ্য, যা interactive সিস্টেমে কেউ আগে থেকে বলতে পারে না (batch যুগের সমাধান ভাবনার প্রশ্ন ১-এ)। একমাত্র পথ অতীত দেখে অনুমান: যে প্রসেস এতক্ষণ ছোট ছোট burst দিয়েছে, তার পরের burst-ও সম্ভবত ছোট।

সবচেয়ে প্রচলিত অনুমান-পদ্ধতি exponential averaging. ধরুন t(n) = n-তম burst-এর আসল (মাপা) দৈর্ঘ্য, আর τ(n) = সেই burst-এর জন্য আগে থেকে করা অনুমান। পরের burst-এর অনুমান:

τ(n+1) = α · t(n) + (1 − α) · τ(n),    0 ≤ α ≤ 1

অর্থাৎ নতুন অনুমান হলো “সর্বশেষ মাপ” আর “পুরোনো অনুমান”-এর একটি মিশ্রণ, আর α ঠিক করে কোনটির ওজন কত। সূত্রটি বারবার বসালে দেখা যায় কেন একে “exponential” বলে:

τ(n+1) = α·t(n) + (1−α)·α·t(n−1) + (1−α)²·α·t(n−2) + … + (1−α)ⁿ⁺¹·τ(0)

প্রতিটি পুরোনো মাপের ওজন (1 − α) গুণ করে করে কমে — জ্যামিতিক হারে। α = ০.৫ হলে সর্বশেষ মাপের ওজন ১/২, তার আগেরটির ১/৪, তার আগের ১/৮… আর সাতটি burst পরে প্রথম অনুমান τ(0)-এর ওজন মাত্র (১/২)⁷ = ১/১২৮. দুটি প্রান্তিক মান পরীক্ষায় প্রায়ই জিজ্ঞেস করা হয়:

α = 0

τ(n+1) = τ(n). সাম্প্রতিক মাপের কোনো ওজন নেই — অনুমান চিরকাল প্রথম অনুমান τ(0)-ই থাকে। প্রসেসের আচরণ বদলালেও scheduler টের পায় না।

শুধু ইতিহাস

α = 1

τ(n+1) = t(n). শুধু সর্বশেষ burst-ই পরের অনুমান — ইতিহাসের কোনো ভূমিকা নেই। একবারের ব্যতিক্রমী লম্বা বা ছোট burst-এ অনুমান লাফিয়ে ওঠে-নামে।

শুধু সাম্প্রতিক

মাঝের α (প্রায়ই ০.৫)

সাম্প্রতিক আর পুরোনো দুটোকেই গুরুত্ব দেয়। বড় α আচরণের বদলে দ্রুত সাড়া দেয় কিন্তু ছটফটে; ছোট α স্থির কিন্তু ধীরে মানিয়ে নেয়।

ভারসাম্য

সমাধান করা উদাহরণ — α = ০.৫, τ(0) = ১০

একটি প্রসেসের পরপর সাতটি burst মাপা হয়েছে: ৬, ৪, ৬, ৪, ১৩, ১৩, ১৩ ms. প্রথম অনুমান τ(0) = ১০ ms (যেমন সিস্টেমের গড়)। প্রতিটি ধাপ: নতুন অনুমান = (আসল + পুরোনো অনুমান) ÷ ২.

nঅনুমান τ(n)আসল t(n)পরের অনুমান τ(n+1) = ০.৫·t(n) + ০.৫·τ(n)
০১০৬০.৫×৬ + ০.৫×১০ = ৮
১৮৪২ + ৪ = ৬
২৬৬৩ + ৩ = ৬
৩৬৪২ + ৩ = ৫
৪৫১৩৬.৫ + ২.৫ = ৯
৫৯১৩৬.৫ + ৪.৫ = ১১
৬১১১৩৬.৫ + ৫.৫ = ১২
৭১২?(এখনো মাপা হয়নি)

লক্ষ করুন, n = ৪ থেকে প্রসেসের আচরণ বদলেছে (burst ৪ থেকে ১৩), কিন্তু অনুমান ধীরে ধীরে উঠছে: ৫ → ৯ → ১১ → ১২. তিনটি ১৩-এর পরেও অনুমান ১৩-এ পৌঁছায়নি — প্রতিবার ফাঁকটি অর্ধেক হচ্ছে। নিচের কোড একই হিসাব α = ০.৫, ০, ১ আর ০.৮-এর জন্য পাশাপাশি দেখায়।

// Predict the next CPU burst: tau(n+1) = alpha * t(n) + (1 - alpha) * tau(n)
const actual = [6, 4, 6, 4, 13, 13, 13];   // measured bursts t(0), t(1), ...
const tau0 = 10;                            // initial guess (e.g. a system-wide average)

function predict(alpha) {
  const tau = [tau0];
  for (const t of actual) tau.push(alpha * t + (1 - alpha) * tau[tau.length - 1]);
  return tau;                               // tau[n] = prediction made before burst n
}

const cols = [0.5, 0, 1, 0.8].map(a => ({ a, tau: predict(a) }));
console.log(" n | actual t(n) | " + cols.map(c => ("tau a=" + c.a).padStart(9)).join(" | "));
for (let n = 0; n <= actual.length; n++) {
  const t = n < actual.length ? String(actual[n]) : "?";
  console.log(String(n).padStart(2) + " | " + t.padStart(11) + " | " +
    cols.map(c => c.tau[n].toFixed(2).padStart(9)).join(" | "));
}

// How much does the very first guess still matter after n bursts? weight = (1-a)^n
for (const a of [0.5, 0.8]) {
  console.log("alpha=" + a + ": weight of tau0 after 7 bursts = " + ((1 - a) ** 7).toFixed(6));
}

আউটপুট (নির্ধারিত হিসাব, প্রতিবার একই):

 n | actual t(n) | tau a=0.5 |   tau a=0 |   tau a=1 | tau a=0.8
 0 |           6 |     10.00 |     10.00 |     10.00 |     10.00
 1 |           4 |      8.00 |     10.00 |      6.00 |      6.80
 2 |           6 |      6.00 |     10.00 |      4.00 |      4.56
 3 |           4 |      6.00 |     10.00 |      6.00 |      5.71
 4 |          13 |      5.00 |     10.00 |      4.00 |      4.34
 5 |          13 |      9.00 |     10.00 |     13.00 |     11.27
 6 |          13 |     11.00 |     10.00 |     13.00 |     12.65
 7 |           ? |     12.00 |     10.00 |     13.00 |     12.93
alpha=0.5: weight of tau0 after 7 bursts = 0.007813
alpha=0.8: weight of tau0 after 7 bursts = 0.000013

α = ০.৫ কলাম হাতের টেবিলের সাথে হুবহু মেলে: ১০, ৮, ৬, ৬, ৫, ৯, ১১, ১২. α = ০ সারাক্ষণ ১০ — প্রসেস কী করছে তাতে কিছু যায় আসে না। α = ১ আসল কলামের ঠিক এক ধাপ পিছিয়ে চলে — আচরণ বদলের পর এক burst-এই ১৩-এ পৌঁছায়, কিন্তু ৬-৪-৬-৪ দোলায় প্রতিবার উল্টো দিকে ভুল করে। α = ০.৮ দ্রুত মানিয়ে নেয় (১১.২৭, ১২.৬৫, ১২.৯৩), তবে দোলায় ছটফট করে (৬.৮০, ৪.৫৬, ৫.৭১). শেষ দুই লাইন: সাত burst পরে প্রথম অনুমানের ওজন α = ০.৫-এ ০.০০৭৮ (= ১/১২৮), α = ০.৮-এ প্রায় শূন্য — প্রাথমিক অনুমান বেশিক্ষণ গুরুত্বপূর্ণ থাকে না।

SRTF-এ অনুমান কীভাবে বসে: অনুমানটি পুরো burst-এর; চলতি প্রসেসের “বাকি সময়” = τ − যতটুকু এই burst-এ ইতিমধ্যে চলেছে। ভুল অনুমানে SRTF ভুল সিদ্ধান্ত নেয় — যেমন অনুমান ছোট বলে একটি প্রসেস বাছা হলো, কিন্তু সে আসলে লম্বা চলল। এজন্যই বাস্তব kernel সরাসরি SJF চালায় না; তার বদলে “যে কম চলেছে/বেশি ঘুমিয়েছে তাকে আগে” — এমন পরোক্ষ নিয়ম ব্যবহার করে (পর্ব ১৯ আর ২১).

৭ · HRRN — অপেক্ষারও মূল্য আছে

SJF ছোট কাজকে ভালোবাসে, FCFS অপেক্ষাকে সম্মান করে। দুটি মেলানো যায় কি? Highest Response Ratio Next (HRRN) ঠিক সেটি করে। একে সাধারণত Per Brinch Hansen-এর (১৯৭০-এর দশকের শুরু) প্রস্তাব বলে উল্লেখ করা হয়। এটি non-preemptive: CPU খালি হলে প্রতিটি অপেক্ষমাণ প্রসেসের জন্য একটি সংখ্যা হিসাব করা হয় —

Response ratio R = (W + S) ÷ S = 1 + W ÷ S
W = এ পর্যন্ত অপেক্ষা (বর্তমান সময় − AT), S = burst

— আর যার R সবচেয়ে বেশি, সে CPU পায় (সমান হলে আগে আসা জন, তারপর টেবিলের ক্রম)। S হরে থাকায় সমান অপেক্ষার পর ছোট কাজের R বেশি — SJF-এর মতো সুবিধা। W লবে থাকায় অপেক্ষার সাথে R বাড়তেই থাকে, ঊর্ধ্বসীমা নেই, আর নতুন আসা কাজের R শুরু হয় ১ থেকে — তাই দীর্ঘ অপেক্ষার কাজ একসময় সবাইকে ছাড়িয়ে যাবেই। Starvation অসম্ভব — aging-এর একটি সুন্দর গাণিতিক রূপ।

R-এর একটি সহজ মানে আছে: (W + S) হলো কাজটি শেষ হতে মোট যত সময় ব্যবস্থায় থাকবে (এখনই শুরু হলে), আর S তার নিজের দৈর্ঘ্য। R = ৩ মানে “নিজের দৈর্ঘ্যের তিন গুণ সময় লাইনে কাটাতে হচ্ছে।” HRRN প্রতিবার সবচেয়ে বেশি “অন্যায়ের শিকার” কাজটিকে আগে নেয়। তবে SJF-এর মতো এটিও burst-এর দৈর্ঘ্য জানতে বা অনুমান করতে চায়।

উদাহরণ (অংশ ৮-এর পূর্ণ সমস্যার টেবিল, AT/BT): P1 1/3, P2 3/6, P3 5/4, P4 7/5, P5 9/2, P6 23/3. নিচে প্রতিটি সিদ্ধান্তের মুহূর্তে R-এর হিসাব দেখুন।

1t=0   ready: none           -> idle 0-12t=1   ready: P1             -> P1 1-43t=4   ready: P2             -> P2 4-104t=10  ready: P3 P4 P5       -> P3 10-145t=14  ready: P4 P5          -> P5 14-166t=16  ready: P4             -> P4 16-217t=21  ready: none -> idle 21-23, P6 23-268done: avg TAT 7.17, avg WT 3.33
সিদ্ধান্তের হিসাব
সময় t
প্রার্থী (W, S)
R = (W+S)/S
বাছাই
  1. t = ০: কেউ আসেনি। প্রথম প্রসেস P1 আসবে t = ১-এ। CPU ০–১ idle — Gantt chart-এ এই খোপটি লিখতে ভুলবেন না।
  2. t = ১: শুধু P1. একমাত্র প্রার্থীর R হিসাবের দরকার নেই (W = ০, R = ১). P1 চলে ১–৪, অপেক্ষা ০.
  3. t = ৪: শুধু P2 (এসেছে ৩-এ, W = ১). R = (১ + ৬)/৬ ≈ ১.১৭. একা, তাই P2 চলে ৪–১০. Non-preemptive, তাই এর মধ্যে P3, P4, P5 এলেও সে থামে না।
  4. t = ১০: তিনজন অপেক্ষায় — আসল সিদ্ধান্ত। P3: W = ১০ − ৫ = ৫, R = ৯/৪ = ২.২৫. P4: W = ৩, R = ৮/৫ = ১.৬০. P5: W = ১, R = ৩/২ = ১.৫০. সবচেয়ে বেশি P3 — যদিও সবচেয়ে ছোট P5. SJF এখানে P5 নিত; HRRN দেখল P3 অনেকক্ষণ বসে আছে। P3 চলে ১০–১৪.
  5. t = ১৪: P4 আর P5. P4: W = ৭, R = ১২/৫ = ২.৪০. P5: W = ৫, R = ৭/২ = ৩.৫০. এবার ছোট কাজটি জেতে — তার অপেক্ষা নিজের দৈর্ঘ্যের আড়াই গুণ হয়ে গেছে। P5 চলে ১৪–১৬.
  6. t = ১৬: শুধু P4. W = ৯, R = ১৪/৫ = ২.৮০. P4 চলে ১৬–২১.
  7. t = ২১: ready queue খালি। P6 আসবে ২৩-এ, তাই CPU ২১–২৩ idle. তারপর P6 একা, চলে ২৩–২৬, অপেক্ষা ০.
  8. ফলাফল। CT: ৪, ১০, ১৪, ২১, ১৬, ২৬. TAT: ৩, ৭, ৯, ১৪, ৭, ৩ → গড় ৪৩/৬ ≈ ৭.১৭. WT: ০, ১, ৫, ৯, ৫, ০ → গড় ২০/৬ ≈ ৩.৩৩. SJF-এ P5-এর অপেক্ষা ছিল ১ কিন্তু P3-এর ৭; HRRN দুজনের মাঝে ভাগ করে দিল (৫ আর ৫).

একটি সূক্ষ্ম পর্যবেক্ষণ: সব প্রসেস একসাথে (t = ০) এলে প্রথম সিদ্ধান্তে সবার W = ০, তাই সবার R = ১ — tie, আর tie-নিয়মে FCFS-এর প্রথম জন জেতে, burst যত বড়ই হোক। যেমন burst ২৪, ৩, ৩-এ HRRN প্রথমে ২৪-এর কাজটিই নেয়, আর পরের সিদ্ধান্তে দুটি ৩-এর কাজের R সমান (২৭/৩ = ৯), তাই ফল হুবহু FCFS-এর মতো: গড় WT ১৭. HRRN-এর সুবিধা দেখা যায় যখন প্রসেসগুলো ভিন্ন সময়ে আসে।

৮ · পরীক্ষার পূর্ণ সমস্যা — চার অ্যালগরিদম পাশাপাশি

বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর লিখিত পরীক্ষায় scheduling-এর অঙ্ক সাধারণত এমন আসে: একটি process table দিয়ে কয়েকটি অ্যালগরিদমের Gantt chart, প্রতিটি প্রসেসের TAT/WT, গড়, আর কোনটি ভালো তার মন্তব্য চাওয়া হয়। নিচের সমস্যায় দুটি ফাঁদ ইচ্ছা করে রাখা হয়েছে: শুরুতে CPU idle (প্রথম প্রসেস t = ১-এ আসে), আর মাঝখানে আরেকটি idle ফাঁক (P6 আসে অনেক পরে)।

প্রশ্ন: ছয়টি প্রসেসের arrival আর burst time (ms) নিচে দেওয়া হলো। FCFS, SJF (non-preemptive), SRTF আর HRRN-এর Gantt chart আঁকুন; প্রতিটির গড় turnaround, waiting আর response time বের করুন; আর ফলাফল তুলনা করুন। Tie হলে আগে আসা প্রসেস আগে।

প্রসেসP1P2P3P4P5P6
AT1357923
BT364523

সমাধানের পদ্ধতি — প্রতিটি অ্যালগরিদমে একই

  1. সময় t = ০ থেকে শুরু করুন। CPU খালি হলে দেখুন কারা এসে গেছে (AT ≤ t); কেউ না এলে পরের arrival পর্যন্ত idle লিখুন।
  2. নিয়ম অনুযায়ী একজনকে বাছুন (FCFS: আগে আসা; SJF: ছোট burst; HRRN: বড় R; SRTF: ছোট বাকি সময় — আর SRTF-এ প্রতিটি arrival-এ আবার তুলনা)।
  3. Gantt chart-এ খোপ আঁকুন, শুরু আর শেষের সময় নিচে লিখুন। শেষে মোট দৈর্ঘ্য যাচাই: মোট burst (২৩) + মোট idle = শেষ সময়।
  4. প্রতিটি প্রসেসের CT চার্ট থেকে পড়ুন; TAT = CT − AT, WT = TAT − BT, RT = প্রথম শুরু − AT. একটি যাচাই: সব WT-এর যোগফল = সব TAT-এর যোগফল − ২৩.

চারটি Gantt chart

FCFS │idle│P1 │P2    │P3  │P4   │P5 │idle│P6 │
     0    1   4      10   14    19  21   23  26

SJF  │idle│P1 │P2    │P5 │P3  │P4   │idle│P6 │
     0    1   4      10  12   16    21   23  26

SRTF │idle│P1 │P2│P3  │P5 │P2   │P4   │idle│P6 │
     0    1   4  5    9   11    16    21   23  26

HRRN │idle│P1 │P2    │P3  │P5 │P4   │idle│P6 │
     0    1   4      10   14  16    21   23  26

যেখানে নীতিগুলো আলাদা হয়: t = ৪ পর্যন্ত সবাই একই — শুধু P1 থাকে, আর SRTF-এ t = ৩-এ P2 এলেও P1-এর বাকি ১ < ৬, তাই preemption নেই। SRTF t = ৫-এ P3 (৪) দিয়ে P2-কে (বাকি ৫) থামায়; t = ৭-এ P4 (৫) আসে, কিন্তু P3-এর বাকি ২, তাই কিছু হয় না; t = ৯-এ P3 শেষ, P5 (২) আসে — প্রার্থী P2 (৫), P4 (৫), P5 (২) → P5; t = ১১-এ P2 আর P4-এর বাকি সমান (৫) — tie-নিয়মে আগে আসা P2. SJF t = ১০-এ সবচেয়ে ছোট P5 নেয়, HRRN নেয় সবচেয়ে বেশিক্ষণ বসে থাকা P3 (অংশ ৭). FCFS শুধু আসার ক্রম মানে, তাই সবচেয়ে ছোট P5 সবার শেষে — অপেক্ষা ১০.

প্রসেস (AT/BT)FCFS: CT / WTSJF: CT / WTSRTF: CT / WT / RTHRRN: CT / WT
P1 (1/3)4 / 04 / 04 / 0 / 04 / 0
P2 (3/6)10 / 110 / 116 / 7 / 110 / 1
P3 (5/4)14 / 516 / 79 / 0 / 014 / 5
P4 (7/5)19 / 721 / 921 / 9 / 921 / 9
P5 (9/2)21 / 1012 / 111 / 0 / 016 / 5
P6 (23/3)26 / 026 / 026 / 0 / 026 / 0
মাপFCFSSJFSRTFHRRN
গড় TAT৪৬/৬ = ৭.৬৭৪১/৬ = ৬.৮৩৩৯/৬ = ৬.৫০৪৩/৬ = ৭.১৭
গড় WT২৩/৬ = ৩.৮৩১৮/৬ = ৩.০০১৬/৬ = ২.৬৭২০/৬ = ৩.৩৩
গড় RT৩.৮৩৩.০০১০/৬ = ১.৬৭৩.৩৩
সর্বোচ্চ WT (কে)১০ (P5)৯ (P4)৯ (P4)৯ (P4)
CPU-তে বসানো (dispatch)৬ বার৬ বার৭ বার৬ বার

মন্তব্য (খাতায় যেমন লিখবেন): গড়ে SRTF সেরা, তারপর SJF, HRRN, আর FCFS সবচেয়ে খারাপ — অংশ ৪ আর ৫-এর তত্ত্বের সাথে মেলে। SRTF-এর দাম একটি বাড়তি context switch (t = ৫-এ P2-কে থামানো; ৬-এর বদলে ৭ বার dispatch) আর P2-এর বড় অপেক্ষা (৭), যদিও তার RT মাত্র ১. HRRN গড়ে SJF-এর চেয়ে সামান্য পিছিয়ে, কিন্তু অপেক্ষা বেশি সমানভাবে ভাগ করে (P3 আর P5 দুজনেরই ৫) আর starvation-মুক্ত। CPU utilisation সবার একই: ২৬ ms-এ ২৩ ms ব্যস্ত, ≈ ৮৮.৫% — idle ফাঁক নীতির দোষ নয়, কাজই ছিল না। Throughput-ও সমান: ২৬ ms-এ ৬টি প্রসেস।

Simulator দিয়ে যাচাই

হাতে করা উত্তর যন্ত্র দিয়ে মেলানো সবচেয়ে ভালো অভ্যাস। নিচের simulator সময়কে ১ ms করে এগোয়; প্রতিটি নীতির একটি “key” ফাংশন বলে দেয় কে আগে (ছোট key জেতে — তাই HRRN-এর key-তে বিয়োগ চিহ্ন)। Non-preemptive নীতিতে শুধু CPU খালি হলে বাছাই হয়, SRTF-এ প্রতি ms-এ। Tie-নিয়ম কোডেই লেখা: SRTF-এ চলতিটি থাকে, তারপর আগে আসা, তারপর টেবিলের ক্রম।

// FCFS, SJF, SRTF, HRRN on one table. Time moves 1 ms at a time.
// Ties: SRTF keeps the running process; then earlier arrival, then table order.
const table = [["P1", 1, 3], ["P2", 3, 6], ["P3", 5, 4],   // [name, arrival, burst]
               ["P4", 7, 5], ["P5", 9, 2], ["P6", 23, 3]];
const key = {                                // smaller key is chosen first
  FCFS: (p, t) => p.at,   SJF: (p, t) => p.bt,
  SRTF: (p, t) => p.left,
  HRRN: (p, t) => -((t - p.at) + p.bt) / p.bt,   // -(W + S) / S
};
function run(alg) {
  const ps = table.map(([n, at, bt], i) => ({ n, at, bt, i, left: bt, start: -1 }));
  const g = []; let t = 0, cur = null, done = 0;
  while (done < ps.length) {
    if (!cur || alg === "SRTF") {            // non-preemptive: decide only when CPU is free
      const k = p => key[alg](p, t), diff = (a, b) => Math.abs(k(a) - k(b)) < 1e-9 ? 0 : k(a) - k(b);
      const ready = ps.filter(p => p.at <= t && p.left > 0);
      ready.sort((a, b) => diff(a, b) || (b === cur) - (a === cur) || a.at - b.at || a.i - b.i);
      cur = ready[0] || null;
    }
    const lab = cur ? cur.n : "idle", last = g[g.length - 1];
    if (last && last.lab === lab) last.end++; else g.push({ lab, s: t, end: t + 1 });
    if (cur && cur.start < 0) cur.start = t;
    if (cur && --cur.left === 0) { cur.ct = t + 1; done++; cur = null; }
    t++;
  }
  console.log(alg + ": " + g.map(x => x.s + "-" + x.end + " " + x.lab).join(" | "));
  const sum = [0, 0, 0];
  for (const p of ps) {
    const v = [p.ct - p.at, p.ct - p.at - p.bt, p.start - p.at];   // TAT, WT, RT
    v.forEach((x, j) => sum[j] += x);
    console.log(`  ${p.n} CT=${p.ct} TAT=${v[0]} WT=${v[1]} RT=${v[2]}`);
  }
  const [a, w, r] = sum.map(x => (x / ps.length).toFixed(2));
  console.log(`  avg TAT=${a} WT=${w} RT=${r}`);
}
["FCFS", "SJF", "SRTF", "HRRN"].forEach(run);

আউটপুট (নির্ধারিত — প্রতিবার একই):

FCFS: 0-1 idle | 1-4 P1 | 4-10 P2 | 10-14 P3 | 14-19 P4 | 19-21 P5 | 21-23 idle | 23-26 P6
  P1 CT=4 TAT=3 WT=0 RT=0
  P2 CT=10 TAT=7 WT=1 RT=1
  P3 CT=14 TAT=9 WT=5 RT=5
  P4 CT=19 TAT=12 WT=7 RT=7
  P5 CT=21 TAT=12 WT=10 RT=10
  P6 CT=26 TAT=3 WT=0 RT=0
  avg TAT=7.67 WT=3.83 RT=3.83
SJF: 0-1 idle | 1-4 P1 | 4-10 P2 | 10-12 P5 | 12-16 P3 | 16-21 P4 | 21-23 idle | 23-26 P6
  P1 CT=4 TAT=3 WT=0 RT=0
  P2 CT=10 TAT=7 WT=1 RT=1
  P3 CT=16 TAT=11 WT=7 RT=7
  P4 CT=21 TAT=14 WT=9 RT=9
  P5 CT=12 TAT=3 WT=1 RT=1
  P6 CT=26 TAT=3 WT=0 RT=0
  avg TAT=6.83 WT=3.00 RT=3.00
SRTF: 0-1 idle | 1-4 P1 | 4-5 P2 | 5-9 P3 | 9-11 P5 | 11-16 P2 | 16-21 P4 | 21-23 idle | 23-26 P6
  P1 CT=4 TAT=3 WT=0 RT=0
  P2 CT=16 TAT=13 WT=7 RT=1
  P3 CT=9 TAT=4 WT=0 RT=0
  P4 CT=21 TAT=14 WT=9 RT=9
  P5 CT=11 TAT=2 WT=0 RT=0
  P6 CT=26 TAT=3 WT=0 RT=0
  avg TAT=6.50 WT=2.67 RT=1.67
HRRN: 0-1 idle | 1-4 P1 | 4-10 P2 | 10-14 P3 | 14-16 P5 | 16-21 P4 | 21-23 idle | 23-26 P6
  P1 CT=4 TAT=3 WT=0 RT=0
  P2 CT=10 TAT=7 WT=1 RT=1
  P3 CT=14 TAT=9 WT=5 RT=5
  P4 CT=21 TAT=14 WT=9 RT=9
  P5 CT=16 TAT=7 WT=5 RT=5
  P6 CT=26 TAT=3 WT=0 RT=0
  avg TAT=7.17 WT=3.33 RT=3.33

চারটি Gantt লাইন উপরের হাতে আঁকা চার্টের সাথে হুবহু মেলে, idle খোপসহ। প্রতিটি প্রসেসের CT/WT উপরের টেবিলে বসানো, আর শেষ লাইনগুলোর গড় (৭.৬৭ / ৬.৮৩ / ৬.৫০ / ৭.১৭ আর ৩.৮৩ / ৩.০০ / ২.৬৭ / ৩.৩৩) তুলনার টেবিলের সংখ্যা। Non-preemptive তিনটিতে RT = WT, কারণ প্রত্যেকে একবারেই পুরো burst চালায়; শুধু SRTF-এ P2-এর RT (১) আর WT (৭) আলাদা। table বদলে নিজের প্রশ্ন চালান — যেমন [["P1", 0, 24], ["P2", 0, 3], ["P3", 0, 3]] দিলে FCFS-এ গড় WT ১৭.০০ আর SJF-এ ৩.০০ পাবেন (অংশ ২); অংশ ৩-এর টেবিলে SJF ৪.০০ আর SRTF ৩.০০ (অংশ ৫).

পরীক্ষার খাতায় প্রচলিত ভুল: (১) প্রথম প্রসেস t = ০-তে না এলে চার্ট ০ থেকে না শুরু করা, বা মাঝের idle ফাঁক বাদ দেওয়া — তাহলে সব CT ভুল হয়। (২) Non-preemptive SJF-এ এখনো না আসা প্রসেসকে বাছা। (৩) SRTF-এ নতুন প্রসেসের burst-কে চলতিটির মোট burst-এর সাথে তুলনা করা — তুলনা হবে বাকি সময়ের সাথে। (৪) TAT-এর জায়গায় CT লিখে ফেলা (AT ০ না হলে দুটি আলাদা)। (৫) Tie-নিয়ম না লেখা — প্রশ্নে বলা না থাকলে নিজের নিয়মটি স্পষ্ট লিখে দিন।

৯ · বাস্তব OS-এ এই নীতিগুলো

খাঁটি SJF বা SRTF কোনো সাধারণ-ব্যবহারের OS-এর প্রধান scheduler নয় — burst জানা যায় না, আর লম্বা কাজের starvation গ্রহণযোগ্য নয়। কিন্তু এদের ধারণা সবখানে ছড়িয়ে আছে:

  • Linux-এর SCHED_FIFO: real-time thread-এর জন্য একটি নীতি — প্রতিটি অগ্রাধিকার-স্তরের ভেতরে খাঁটি FCFS. Task নিজে block বা yield না করলে, বা বেশি অগ্রাধিকারের কেউ না এলে, CPU ছাড়ে না। পাশেই SCHED_RR, যা একই জিনিস কিন্তু time quantum-সহ। সাধারণ প্রসেস চলে SCHED_OTHER নীতিতে (Linux 6.6 থেকে EEVDF, তার আগে CFS — পর্ব ২১). Windows-এও real-time অগ্রাধিকার শ্রেণি আছে, তবে সেখানে একই অগ্রাধিকারের thread-রা quantum ধরে পালা করে।
  • “কম চলেছে, তাকে আগে”: Interactive প্রসেস (editor, shell) সাধারণত ছোট burst দিয়েই ঘুমিয়ে পড়ে; আধুনিক scheduler জেগে ওঠা এমন প্রসেসকে দ্রুত CPU দেয় — পরোক্ষভাবে “ছোট burst আগে” (পর্ব ১৯-এর MLFQ).
  • Batch আর HPC cluster: Slurm-এর মতো job scheduler-এ ব্যবহারকারী প্রতিটি job-এর সর্বোচ্চ সময় ঘোষণা করেন, আর scheduler সেটি দিয়ে “backfill” করে — বড় job-এর অপেক্ষার ফাঁকে ছোট job ঢোকায়, যদি তাতে বড়টির শুরু না পিছোয়।

Linux kernel প্রতিটি task-এর “কতক্ষণ CPU-তে চলেছে” আর “কতক্ষণ run queue-তে অপেক্ষা করেছে” — অর্থাৎ ঠিক এই পর্বের BT আর WT — ন্যানোসেকেন্ডে গুনে রাখে, আর /proc/<pid>/schedstat ফাইলে দেখায় (তিনটি সংখ্যা: CPU-তে সময়, অপেক্ষার সময়, কতগুলো timeslice). নিচের script দুটি অসীম লুপকে taskset দিয়ে একই CPU-তে বেঁধে এক সেকেন্ড চালায়, তারপর kernel-এর হিসাব পড়ে।

# Two CPU-hungry loops share ONE CPU for about 1 second.
# /proc/PID/schedstat = ns on the CPU, ns waiting in the run queue, timeslices
cpu=$(taskset -cp $$ | sed 's/.*: //; s/[-,].*//')     # first CPU this shell may use
echo "policy of this shell: $(chrt -p $$ | head -1 | sed 's/.*: //')"
pids=""
for i in 1 2; do
  taskset -c "$cpu" sh -c 'while :; do :; done' &
  pids="$pids $!"
done
sleep 1
i=0
for p in $pids; do
  i=$((i + 1))
  read -r run wait slices < "/proc/$p/schedstat"
  echo "loop $i: on-cpu $((run / 1000000)) ms, waiting $((wait / 1000000)) ms, slices $slices"
done
{ kill $pids; wait $pids; } 2>/dev/null
echo "both loops stopped"

একটি আসল রানের আউটপুট:

policy of this shell: SCHED_OTHER
loop 1: on-cpu 464 ms, waiting 542 ms, slices 435
loop 2: on-cpu 464 ms, waiting 544 ms, slices 486
both loops stopped

প্রথম লাইন: সাধারণ প্রসেসের নীতি SCHED_OTHER (Linux-এর ন্যায্য-ভাগের scheduler). দুটি loop: একই CPU-তে দুজনেই সবসময় চলতে প্রস্তুত, তাই kernel CPU প্রায় সমান ভাগ করেছে (৪৬৪ আর ৪৬৪ ms), আর প্রত্যেকে প্রায় ৫৪০ ms ready queue-তে অপেক্ষা করেছে — এই পর্বের WT, যা kernel নিজেই মেপে রাখে। কয়েকশো slice মানে প্রতি টুকরো গড়ে প্রায় ১ ms — FCFS-এর মতো কেউ টানা চলেনি, preemptive পালাবদল হয়েছে। অপেক্ষা CPU-সময়ের চেয়ে কিছুটা বেশি, সম্ভবত container-এর অন্য কাজও এই CPU ভাগ করেছে বলে; আরেকটি রানে এসেছিল ৩৯৯ ms চলা / ৬০৯ ms অপেক্ষা। সংখ্যা বদলায়, কিন্তু দুজনের সমান ভাগ আর লম্বা অপেক্ষার ছবিটি একই থাকে।

নীতি দেখতে বা বদলাতে Linux-এ chrt কমান্ড আছে। Real-time নীতি দিতে সাধারণত বিশেষ অনুমতি (root বা CAP_SYS_NICE) লাগে, কারণ একটি real-time অসীম লুপ পুরো CPU দখল করতে পারে; সুরক্ষা হিসেবে ডিফল্টে real-time task-রা প্রতি সেকেন্ডে সর্বোচ্চ ০.৯৫ সেকেন্ড পায়। উদাহরণ (চালানোর কোড নয়):

$ chrt -p $$                       # current policy of this shell
pid 4120's current scheduling policy: SCHED_OTHER
pid 4120's current scheduling priority: 0
$ cat /proc/sys/kernel/sched_rt_runtime_us
950000
$ sudo chrt -f 10 ./sensor_logger   # run with SCHED_FIFO, priority 10 (needs privilege)

আরও গভীরে: SRTF-এর তাত্ত্বিক নাম SRPT, আর এক CPU-তে গড় response (flow) time কমাতে এটি সর্বোত্তম — L. Schrage-এর ১৯৬৮ সালের প্রমাণ। Mor Harchol-Balter ও সহকর্মীরা ২০০০-এর দশকের শুরুতে দেখান, ওয়েব সার্ভারে স্থির ফাইলের অনুরোধ ফাইলের আকার অনুযায়ী (ছোট আগে) পাঠালে গড় response time অনেক কমে, আর ইন্টারনেটের অনুরোধের আকার “heavy-tailed” হওয়ায় বড় অনুরোধের ক্ষতি আশঙ্কার চেয়ে অনেক কম। অন্যদিকে, arrival-সহ non-preemptive ক্ষেত্রে সর্বোত্তম ক্রম খোঁজা NP-hard; তাই সেখানে SJF একটি ভালো কিন্তু নিখুঁত নয় এমন heuristic.

১০ · AI-এর যুগে scheduling-এর অঙ্ক

“এই টেবিলের SRTF Gantt chart আর গড় waiting time চাই” — AI-কে এমন বললে আত্মবিশ্বাসী উত্তর পাবেন, কিন্তু scheduling-এর অঙ্কে AI-এর ভুল খুব চেনা ধরনের। উত্তর নেওয়ার আগে এগুলো খুঁজুন:

AI-এর ভুলকেন ভুলকীভাবে ধরবেন
Idle ফাঁক বাদ দিয়ে চার্ট সংকুচিত করাসব পরের CT ভুল হয়ে যায়শেষ সময় = মোট burst + মোট idle কি না মেলান
Non-preemptive SJF-এ ভবিষ্যতের প্রসেস বাছাসিদ্ধান্তের মুহূর্তে যে আসেনি, সে প্রার্থী নয়প্রতিটি খোপের শুরুতে বাছাই করা প্রসেসের AT ≤ শুরুর সময় কি না দেখুন
SRTF-এ মোট burst দিয়ে তুলনাতুলনা হবে বাকি সময়ের সাথেপ্রতিটি arrival-এ সবার বাকি সময় লিখে রাখুন (অংশ ৫-এর মতো)
গড়ের যোগফলে গণিতের ভুল, বা TAT আর WT গুলিয়ে ফেলাভাষার মডেল অঙ্ক “অনুমান” করতে পারেΣWT = ΣTAT − ΣBT মেলান; simulator চালান
“SJF সবসময় সর্বোত্তম” বা “Linux SJF ব্যবহার করে”শর্ত ছাড়া দাবি; বাস্তব kernel SJF চালায় নাঅংশ ৪-এর শর্তগুলো আর পর্ব ২১ মিলিয়ে দেখুন

AI-কে scheduling-এর অঙ্ক দিলে নিয়ম আর যাচাইয়ের শর্তসহ দিন:

নিচের process table-এর জন্য SRTF (preemptive SJF) scheduling করো। P1: AT 1, BT 3 · P2: AT 3, BT 6 · P3: AT 5, BT 4 · P4: AT 7, BT 5 · P5: AT 9, BT 2 · P6: AT 23, BT 3 (সময় ms-এ) নিয়ম: context switch-এর খরচ শূন্য; বাকি সময় সমান হলে চলতি প্রসেস চলতে থাকবে, নইলে আগে আসা প্রসেস; CPU খালি থাকলে idle হিসেবে দেখাবে। ১. প্রতিটি arrival আর completion-এর মুহূর্তে সবার বাকি সময় একটি টেবিলে দেখাও। ২. Idle খোপসহ পূর্ণ Gantt chart দাও। ৩. প্রতিটি প্রসেসের CT, TAT = CT − AT, WT = TAT − BT, RT লেখো, তারপর গড়। ৪. শেষে যাচাই করো: শেষ সময় = মোট burst + মোট idle, আর ΣWT = ΣTAT − ΣBT. ৫. কোনো ধাপে নিশ্চিত না হলে স্পষ্ট বলো, অনুমান করে সংখ্যা বসাবে না।

AI-এর উত্তর মানার আগে — চেকলিস্ট

পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:

  • Convoy effect কী, কোন নীতিতে হয়? — FCFS-এ একটি লম্বা (CPU-bound) কাজের পেছনে অনেক ছোট কাজ আটকে থাকা; গড় অপেক্ষা বাড়ে, I/O যন্ত্রও অলস থাকে।
  • SJF কেন বাস্তবে সরাসরি ব্যবহার হয় না? — পরের CPU burst আগে জানা যায় না (শুধু অনুমান করা যায়), আর লম্বা কাজের starvation হতে পারে।
  • SJF আর SRTF-এর পার্থক্য? — SJF non-preemptive, শুধু CPU খালি হলে বাছে; SRTF preemptive, ছোট বাকি সময়ের কেউ এলে চলতিটিকে থামায়।
  • Exponential averaging-এ α = 0 আর α = 1 মানে? — α = 0: অনুমান কখনো বদলায় না (শুধু τ0); α = 1: শুধু সর্বশেষ burst-ই পরের অনুমান।
  • HRRN কীভাবে starvation ঠেকায়? — R = 1 + W/S; অপেক্ষা W বাড়লে R সীমাহীনভাবে বাড়ে, তাই দীর্ঘ অপেক্ষার কাজ একসময় সর্বোচ্চ R পায়।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১SJF প্রমাণিতভাবে সর্বোত্তম, তবু কোনো জনপ্রিয় ডেস্কটপ বা সার্ভার OS একে প্রধান scheduler বানায়নি কেন? Batch যুগে কীভাবে চলত?

অংশ ৪-এর প্রমাণের শর্তগুলো আবার পড়ুন: burst জানা, সবাই একসাথে উপস্থিত, আর লক্ষ্য শুধু গড় অপেক্ষা। বাস্তব OS-এ তিনটি শর্তই ভাঙে। Interactive প্রসেসের পরের CPU burst কেউ জানে না — ব্যবহারকারী কখন কী চাপবেন, নেটওয়ার্ক থেকে কত ডেটা আসবে, তার ওপর নির্ভর করে। প্রসেস আসে আর যায় সারাক্ষণ। আর লক্ষ্য শুধু গড় নয়: কোনো একটি প্রসেস অনির্দিষ্টকাল আটকে থাকলে (starvation) গড় যত ভালোই হোক, সিস্টেমটি অগ্রহণযোগ্য।

১৯৬০-এর দশকের batch সিস্টেমে ব্যাপারটি আলাদা ছিল। ব্যবহারকারী job জমা দিতেন punched card-এ, সঙ্গে job control card-এ সময়-সীমা লিখে। অপারেটর বা scheduler সেই ঘোষণা দেখে ছোট job আগে চালাতে পারত। ঘোষণায় ফাঁকি দেওয়া ঠেকাতে নিয়ম ছিল কঠোর: ঘোষিত সময় পেরোলেই job বাতিল, আর হিসাব-নিকাশের বিল সময় অনুযায়ী। অর্থাৎ SJF তখন কাজ করত কারণ মানুষ নিজেই অনুমান দিত, আর ভুল অনুমানের শাস্তি ছিল। আজকের HPC cluster-এর scheduler (যেমন Slurm)-এও একই কৌশল টিকে আছে — ব্যবহারকারী job-এর সর্বোচ্চ সময় বলে দেন, আর scheduler সেটি backfill-এ কাজে লাগায়।

সাধারণ OS তাই অন্য পথ নিয়েছে: SJF-এর লক্ষ্যটি (ছোট burst-এর প্রসেস দ্রুত সাড়া পাক) রেখে, burst-এর জ্ঞান ছাড়াই। পর্ব ১৯-এর MLFQ দেখে প্রসেসটি তার সময় পুরো খরচ করছে কি না — করলে নিচের সারিতে নামায়, না করলে (মানে ছোট burst) ওপরে রাখে। Linux-এর CFS আর EEVDF দেখে “কে কত কম CPU পেয়েছে”, আর Windows I/O শেষ হওয়া thread-কে সাময়িক priority boost দেয়। সবগুলোই অতীত আচরণ দেখে ভবিষ্যৎ অনুমান, সঙ্গে সবার জন্য ন্যূনতম নিশ্চয়তা।

মূল উপলব্ধি: তাত্ত্বিক সর্বোত্তমতা একটি নির্দিষ্ট মডেলের ভেতরে সত্য। বাস্তব scheduler-এর কাজ হলো সেই আদর্শের যতটা কাছে যাওয়া যায়, তথ্যের অভাব আর ন্যায্যতার দাবি মেনে নিয়ে।

প্র ০২Convoy effect কি শুধু CPU scheduling-এর সমস্যা? ডেটাবেসের lock, ডিস্ক আর নেটওয়ার্কে এর চেহারা কেমন?

Convoy-র মূল কারণ একটাই: একটি ভাগের সম্পদ, একটি FIFO সারি, আর সারির মাথায় একটি ধীর বা লম্বা কাজ। এই তিনটি যেখানে মেলে, সেখানেই convoy. CPU তার একটি উদাহরণ মাত্র।

Lock convoy: ১৯৭০-এর দশকের শেষে IBM-এর System R ডেটাবেস প্রকল্পে Blasgen, Gray ও সহকর্মীরা “convoy phenomenon” নামে একটি সমস্যা বর্ণনা করেন। একটি ঘন ঘন ব্যবহৃত lock ধরে থাকা প্রসেস যদি তার quantum শেষে বা page fault-এ থেমে যায়, বাকি সবাই সেই lock-এর সারিতে জমে যায়। Lock ছাড়ার পর সারির প্রথমজন পায়, কিন্তু তাকে জাগাতে context switch লাগে; ততক্ষণে বাকিরা আবার lock চায় আর সারিতে ফেরে। ফলে সিস্টেম একটি “কনভয়”-এ আটকে যায় — সবাই ধীরে, এক ফাইলে, lock-এর গতিতে চলে, আর ভিড় কমলেও সহজে ভাঙে না। সমাধান হিসেবে lock ছাড়ার সময় সরাসরি হস্তান্তর না করে সবাইকে আবার প্রতিযোগিতা করতে দেওয়ার মতো কৌশল ব্যবহার হয় (মডিউল ৪-এ lock-এর আলোচনা)।

নেটওয়ার্কে head-of-line blocking: HTTP/1.1-এ একটি সংযোগে অনুরোধগুলোর উত্তর ক্রমানুসারে আসতে হতো; একটি ধীর উত্তর পেছনের সব ছোট উত্তর আটকে দিত। HTTP/2 একটি সংযোগে অনেক stream পাশাপাশি চালিয়ে এই সমস্যার HTTP-স্তরের রূপ দূর করে, কিন্তু নিচের TCP তখনো সব byte ক্রমানুসারে দিতে বাধ্য — একটি হারানো packet সব stream আটকায়। QUIC (HTTP/3) প্রতিটি stream-কে আলাদাভাবে ক্রম রাখতে দিয়ে এই স্তরেও convoy কমায়।

ডিস্কে একটি বড় ক্রমিক লেখার পেছনে ছোট random read আটকে থাকতে পারে, তাই I/O scheduler আলাদা সারি বা সময়সীমা রাখে (পর্ব ৪০). সুপারশপে “১০টির কম পণ্য” লেখা আলাদা কাউন্টারও convoy ভাঙার একটি SJF-জাতীয় কৌশল।

মূল উপলব্ধি: FIFO সারি সরল আর ন্যায্য দেখায়, কিন্তু সেবার সময়ের পার্থক্য বড় হলে convoy অনিবার্য। সমাধান প্রায় সবসময় একই ধাঁচের — ছোট কাজের আলাদা পথ, preemption, বা সারি ভেঙে একাধিক স্বাধীন সারি।

প্র ০৩SRTF লম্বা কাজের প্রতি “অন্যায্য” — এই ভয় বাস্তবে কতটা সত্য? ওয়েব সার্ভারের গবেষণা কী বলে?

সরল যুক্তি বলে, ছোট কাজকে সবসময় আগে দিলে বড় কাজ ভুগবেই। Starvation-এর উদাহরণও সত্য: ছোট কাজের স্রোত অবিরাম আর ঘন হলে বড় কাজ কখনো শেষ হবে না। কিন্তু “কতটা ভুগবে” প্রশ্নটির উত্তর নির্ভর করে কাজের আকারের বণ্টনের ওপর, আর লোড কতটা তার ওপর।

২০০০-এর দশকের শুরুতে Carnegie Mellon বিশ্ববিদ্যালয়ের Mor Harchol-Balter ও সহকর্মীরা এই প্রশ্ন গাণিতিকভাবে আর বাস্তব ওয়েব সার্ভারে পরীক্ষা করেন। স্থির ফাইলের ক্ষেত্রে অনুরোধের আকার — অর্থাৎ কাজের দৈর্ঘ্য — আগেই জানা: ফাইলের আকার। তাই সেখানে SRPT সত্যিই চালানো যায়। তাঁরা দেখান, ইন্টারনেটের ফাইলের আকার “heavy-tailed” — বেশিরভাগ অনুরোধ খুব ছোট, আর অল্প কয়েকটি বিশাল অনুরোধ মোট কাজের বড় অংশ নেয়। এমন বণ্টনে SRPT ছোট অনুরোধের অপেক্ষা নাটকীয়ভাবে কমায়, আর সবচেয়ে বড় অনুরোধগুলোও খুব সামান্যই পিছোয় — কারণ তাদের সামনে যে ছোট কাজগুলো ঢোকে, সেগুলো মোট সময়ের ছোট একটি ভাগ। তাদের বিশ্লেষণে মাঝারি লোডে প্রায় সব কাজই PS (সবাই সমান ভাগ, Round Robin-এর আদর্শ রূপ)-এর চেয়ে SRPT-তে ভালো বা প্রায় সমান থাকে; লোড খুব বেশি হলে সবচেয়ে বড় কাজগুলোর ক্ষতি বাড়ে।

তবু SRPT সবখানে নিরাপদ নয়: আকার না জানলে অনুমানের ভুল থাকে, লোড প্রায় ১০০% হলে বড় কাজের অপেক্ষা সত্যিই অনেক বাড়ে, আর কেউ নিজের কাজকে ছোট দেখিয়ে সুবিধা নিতে পারে। তাই বাস্তব সিস্টেম ছোট কাজের অগ্রাধিকারের সাথে aging বা ন্যূনতম ভাগের নিশ্চয়তা মেশায় (পর্ব ১৯-এর MLFQ).

মূল উপলব্ধি: “ন্যায্যতা” একটি পরিমাপযোগ্য প্রশ্ন, শুধু নৈতিক নয়। কাজের আকারের বণ্টন আর লোড না জেনে কোনো নীতিকে ন্যায্য বা অন্যায্য বলা যায় না — মেপে দেখতে হয়।

প্র ০৪Exponential averaging কি শুধু scheduling-এর কৌশল? TCP আর Linux-এর load average-এ একই সূত্র কোথায়, আর α বাছাইয়ের দর-কষাকষি কী?

τ(n+1) = α·t(n) + (1 − α)·τ(n) সূত্রটির সৌন্দর্য হলো এটি মাত্র একটি সংখ্যা মনে রেখে পুরো ইতিহাসের “ওজনদার গড়” দেয় — পুরোনো সব মাপ জমা রাখতে হয় না। Kernel-এর মতো জায়গায়, যেখানে প্রতি প্রসেসে বা প্রতি সংযোগে মেমরি আর সময় দুটোই দামি, এটি আদর্শ। পরিসংখ্যানে একে exponentially weighted moving average (EWMA) বলে।

TCP: নেটওয়ার্কে একটি packet পাঠিয়ে তার স্বীকৃতি (ACK) ফিরে আসতে যে সময় লাগে, তাকে round-trip time (RTT) বলে। কতক্ষণ পরে packet হারিয়েছে ধরে নিয়ে আবার পাঠাতে হবে (retransmission timeout), তা ঠিক করতে TCP RTT-র একটি মসৃণ অনুমান রাখে — একই সূত্রে। ১৯৮০-এর দশকের শেষে Van Jacobson দেখান, শুধু গড় নয়, তারতম্যও (variance) একইভাবে অনুমান করে timeout-এ যোগ করতে হবে; এখনকার মান RFC 6298-এ গড়ের জন্য α = ১/৮ আর তারতম্যের জন্য β = ১/৪ সুপারিশ করা হয়। ছোট α, কারণ একটি packet-এর হঠাৎ দেরিতে timeout লাফিয়ে উঠলে বা নেমে গেলে অকারণ retransmission হয়।

Linux-এর load average: uptime বা top-এ যে তিনটি সংখ্যা (১, ৫ আর ১৫ মিনিটের) দেখেন, সেগুলো চলতে প্রস্তুত (আর Linux-এ uninterruptible অপেক্ষায় থাকা) task-এর সংখ্যার exponential average — নির্দিষ্ট বিরতিতে মাপ নিয়ে তিনটি আলাদা α দিয়ে। ১৫ মিনিটের সংখ্যাটি ধীরে বদলায় (ছোট α), ১ মিনিটেরটি দ্রুত। তাই “১ মিনিটের মান বেশি, ১৫ মিনিটের কম” মানে লোড সাম্প্রতিক সময়ে বেড়েছে।

α-এর দর-কষাকষি সব জায়গায় একই: বড় α দ্রুত সাড়া দেয় কিন্তু গোলমাল (noise)-এ ছটফট করে; ছোট α মসৃণ কিন্তু আসল পরিবর্তন ধরতে দেরি করে। Scheduling-এ বড় α মানে একবারের লম্বা burst-এর পর interactive প্রসেসকে ভুল করে “ভারী” ধরা; ছোট α মানে কম্পাইলার চালু হলেও অনেকক্ষণ তাকে “হালকা” ভাবা। অংশ ৬-এর কোডে α = ০.৮ আর ০.৫-এর কলাম পাশাপাশি দেখলে দুটো প্রবণতাই চোখে পড়ে।

মূল উপলব্ধি: Exponential averaging হলো সীমিত মেমরিতে “সাম্প্রতিক অতীত দিয়ে নিকট ভবিষ্যৎ” অনুমানের সর্বজনীন যন্ত্র; α বাছাই মানে স্থিরতা আর তৎপরতার মধ্যে একটি সচেতন সিদ্ধান্ত।

প্র ০৫FCFS কি কখনো সেরা? গড় না দেখে “সবচেয়ে খারাপ অপেক্ষা” দেখলে কোন নীতি জেতে, আর কেন?

এই পর্বে সবকিছু মাপা হয়েছে গড় দিয়ে, আর গড়ে FCFS সবসময় পিছিয়ে। কিন্তু মাপকাঠি বদলালে বিজয়ীও বদলায়। ধরুন লক্ষ্য হলো সর্বোচ্চ turnaround time (বা সর্বোচ্চ অপেক্ষা) যত কম সম্ভব রাখা — অর্থাৎ সবচেয়ে দুর্ভাগা প্রসেসটির অবস্থা ভালো করা। এক CPU-তে, arrival-সহ, আর CPU-কে অকারণে অলস না রেখে, এই লক্ষ্যে FCFS সর্বোত্তম।

যুক্তিটি ছোট। FCFS-এর চার্টে যে প্রসেস j-এর turnaround সবচেয়ে বেশি, তাকে নিন। j যে টানা ব্যস্ত সময়খণ্ডে চলেছে, সেটি শুরু হয়েছে কোনো এক মুহূর্ত r-এ (তার আগে CPU idle ছিল বা চার্টের শুরু)। FCFS-এ j-এর আগে সেই খণ্ডে যারা চলেছে, তারা সবাই এসেছে r আর j-এর arrival-এর মাঝে, আর j-এর সমাপ্তি = r + তাদের সবার (j-সহ) burst-এর যোগফল। এখন যেকোনো নীতি নিন। এই কাজগুলো কেউই r-এর আগে আসেনি, তাই তাদের সবার শেষ হতে অন্তত ততটাই সময় লাগবে — এদের মধ্যে যে শেষে শেষ হবে, তার সমাপ্তি ≥ FCFS-এ j-এর সমাপ্তি। আর তার arrival ≤ j-এর arrival, তাই তার turnaround ≥ FCFS-এ j-এর turnaround. অর্থাৎ কোনো নীতিই FCFS-এর চেয়ে কম সর্বোচ্চ turnaround দিতে পারে না।

অংশ ৮-এর সমস্যাতেও এর ছায়া দেখা যায়: সর্বোচ্চ TAT FCFS-এ ১২ (P4 আর P5), বাকি তিনটিতে ১৪ (P4) — অথচ গড়ে FCFS সবচেয়ে খারাপ। (সর্বোচ্চ WT সবসময় একই কথা বলে না, কারণ burst ছোট-বড় হলে WT আর TAT-এর ক্রম আলাদা হতে পারে — এখানে FCFS-এ P5-এর WT ১০.) এই কারণেই নেটওয়ার্ক সুইচের সারি, প্রিন্টারের সারি বা অনেক message queue FIFO রাখে: ক্রম রক্ষা হয়, কেউ অনির্দিষ্টকাল আটকে থাকে না, আর সবচেয়ে খারাপ অপেক্ষা সীমিত থাকে। কাজগুলোর আকার যদি প্রায় সমান হয়, FCFS গড়েও প্রায় সেরা — SJF-এর সুবিধা আসে আকারের তারতম্য থেকে।

মূল উপলব্ধি: “সেরা নীতি” বলে কিছু নেই — আছে “কোন মাপকাঠিতে সেরা”. গড় চাইলে SJF/SRTF, সবচেয়ে খারাপ অবস্থা চাইলে FCFS, দুটোর মাঝামাঝি চাইলে HRRN বা Round Robin. পরীক্ষার “তুলনা করুন” প্রশ্নে এই কথাটিই সবচেয়ে বেশি নম্বর আনে।

অনুশীলন

হাতে Gantt chart, সূত্রের হিসাব, আউটপুট অনুমান আর কোড — আগে নিজে চেষ্টা করুন, তারপর উত্তর মেলান।

  1. হাতে Gantt chart: চারটি প্রসেস — P1 (AT 0, BT 8), P2 (AT 1, BT 4), P3 (AT 2, BT 9), P4 (AT 3, BT 5). Non-preemptive SJF আর SRTF-এর Gantt chart, প্রতিটি প্রসেসের WT, আর দুটির গড় WT ও গড় TAT বের করুন।
    SJF  │P1      │P2  │P4   │P3       │
         0        8    12    17        26
    SRTF │P1│P2  │P4   │P1     │P3       │
         0  1    5     10      17        26

    SJF: t = ০-তে শুধু P1, তাই ০–৮. t = ৮-এ P2 (৪), P3 (৯), P4 (৫) → P2, তারপর P4, তারপর P3. WT: P1 ০, P2 ৮ − ১ = ৭, P4 ১২ − ৩ = ৯, P3 ১৭ − ২ = ১৫ → গড় ৩১/৪ = ৭.৭৫; গড় TAT ৫৭/৪ = ১৪.২৫.

    SRTF: t = ১-এ P2 (৪) < P1-এর বাকি ৭ → preemption. t = ২-এ P3 (৯), t = ৩-এ P4 (৫) — কেউ P2-এর বাকির চেয়ে ছোট নয়। t = ৫-এ P2 শেষ; বাকি P1 ৭, P3 ৯, P4 ৫ → P4 (৫–১০), তারপর P1 (১০–১৭), শেষে P3 (১৭–২৬). WT: P1 = ১৭ − ০ − ৮ = ৯, P2 = ০, P3 = ২৬ − ২ − ৯ = ১৫, P4 = ১০ − ৩ − ৫ = ২ → গড় ২৬/৪ = ৬.৫০; গড় TAT ৫২/৪ = ১৩.০০. (Simulator-এ এই টেবিল বসিয়ে একই সংখ্যা পাওয়া যায়।)

  2. সূত্রের হিসাব: একটি প্রসেসের প্রথম অনুমান τ(0) = ১২ ms, α = ০.২৫. পরপর তিনটি burst মাপা হলো ৮, ১৬, ৪ ms. τ(1), τ(2), τ(3) বের করুন। α = ০.২৫ মানে scheduler সাম্প্রতিক মাপকে কতটা গুরুত্ব দিচ্ছে?

    τ(1) = ০.২৫ × ৮ + ০.৭৫ × ১২ = ২ + ৯ = ১১. τ(2) = ০.২৫ × ১৬ + ০.৭৫ × ১১ = ৪ + ৮.২৫ = ১২.২৫. τ(3) = ০.২৫ × ৪ + ০.৭৫ × ১২.২৫ = ১ + ৯.১৮৭৫ = ১০.১৮৭৫ (≈ ১০.১৯).

    α = ০.২৫ মানে সর্বশেষ মাপের ওজন মাত্র এক-চতুর্থাংশ, ইতিহাসের তিন-চতুর্থাংশ। ১৬-এর মতো একটি বড় মাপেও অনুমান ১১ থেকে উঠল মাত্র ১২.২৫-এ — মসৃণ কিন্তু ধীর। α = ০.৫ হলে একই হিসাবে পেতেন ১০, ১৩, ৮.৫.

  3. আউটপুট অনুমান: অংশ ৮-এর simulator-এ table বদলে [["P1", 0, 24], ["P2", 0, 3], ["P3", 0, 3]] করা হলো। HRRN: দিয়ে শুরু হওয়া লাইন আর তার avg লাইনে কী ছাপা হবে? কেন?
    HRRN: 0-24 P1 | 24-27 P2 | 27-30 P3
      avg TAT=27.00 WT=17.00 RT=17.00

    t = ০-তে তিনজনেরই W = ০, তাই R = (০ + S)/S = ১ — সবাই সমান। Tie-নিয়মে arrival-ও সমান, তাই টেবিলের ক্রম: P1. Non-preemptive বলে P1 ০–২৪ চলে। t = ২৪-এ P2 আর P3 দুজনেরই R = (২৪ + ৩)/৩ = ৯ — আবার tie, P2 আগে। ফল হুবহু FCFS: গড় WT ১৭, TAT ২৭. SJF আর SRTF একই টেবিলে দেয় ৩.০০ — সবাই একসাথে এলে HRRN-এর প্রথম বাছাই কার্যত FCFS.

  4. কোড লিখুন: Simulator-এ একটি নতুন non-preemptive নীতি যোগ করুন — LJF (Longest Job First, যার burst সবচেয়ে বড় সে আগে)। কোন লাইনে কী লিখবেন? অংশ ৩-এর টেবিলে (P1 0/7, P2 2/4, P3 4/1, P4 5/4) চালালে গড় WT কত আসবে, আর SJF-এর ৪.০০-এর সাথে তুলনায় এটি কী প্রমাণ করে?

    key বস্তুতে একটি লাইন যোগ করুন, আর চালানোর তালিকায় নামটি:

      LJF:  (p, t) => -p.bt,                 // bigger burst = smaller key
    ...
    ["FCFS", "SJF", "SRTF", "HRRN", "LJF"].forEach(run);

    যেহেতু run SRTF ছাড়া সবাইকে non-preemptive ধরে, আর কিছু বদলাতে হয় না। আউটপুট:

    LJF: 0-7 P1 | 7-11 P2 | 11-15 P4 | 15-16 P3
      avg TAT=9.50 WT=5.50 RT=5.50

    t = ৭-এ P2 আর P4 দুজনেরই burst ৪ (সবচেয়ে বড়) — tie, আগে আসা P2. সবচেয়ে ছোট P3 সবার শেষে, অপেক্ষা ১১. গড় WT ৫.৫০ — SJF-এর ৪.০০ আর FCFS-এর ৪.৭৫-এর চেয়েও খারাপ। অংশ ৪-এর সূত্রের উল্টো দিক: বড় burst-কে বড় গুণকের সাথে জোড়া দিলে মোট অপেক্ষা বাড়ে।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি scheduling numerical সমাধানের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Process table-এর AT আর BT পড়ে tie-নিয়ম ঠিক করা
  2. প্রতিটি সিদ্ধান্তের মুহূর্তে কারা এসে গেছে তা দেখা
  3. নীতি অনুযায়ী একজনকে বেছে Gantt chart-এ খোপ আঁকা (দরকারে idle)
  4. চার্ট থেকে প্রতিটি প্রসেসের CT পড়া
  5. TAT = CT − AT আর WT = TAT − BT হিসাব করা
  6. গড় বের করে ΣWT = ΣTAT − ΣBT দিয়ে যাচাই করা

তিনটি প্রসেস একসাথে t = ০-তে আসে, burst P1 = ২৪, P2 = ৩, P3 = ৩ ms. FCFS-এ P1, P2, P3 ক্রমে চালালে গড় waiting time কত?

  • ৩ ms
  • ১০ ms
  • ১৭ ms
  • ২৭ ms

চারটি প্রসেস: P1 (AT 0, BT 7), P2 (AT 2, BT 4), P3 (AT 4, BT 1), P4 (AT 5, BT 4). SRTF (preemptive SJF)-এ গড় waiting time কত?

  • ৪.০০ ms
  • ৩.০০ ms
  • ৪.৭৫ ms
  • ০.৫০ ms

Exponential averaging-এ α = ০.৫, বর্তমান অনুমান τ(n) = ১০ ms, আর সর্বশেষ মাপা burst t(n) = ৬ ms. পরের burst-এর অনুমান τ(n+1) কত?

  • ৮ ms
  • ৬ ms
  • ১০ ms
  • ১৬ ms

HRRN-এ একটি প্রসেস ৬ ms অপেক্ষা করেছে আর তার burst ৩ ms. তার response ratio কত, আর অপেক্ষা বাড়তে থাকলে এটি কী করে?

  • ২; অপেক্ষার সাথে বদলায় না
  • ০.৩৩; অপেক্ষা বাড়লে কমে
  • ৩; অপেক্ষার সাথে স্থির থাকে
  • ৩; অপেক্ষা বাড়লে বাড়তেই থাকে

“SJF গড় waiting time সর্বনিম্ন করে” — এই উপপাদ্যটি নিচের কোন শর্তে প্রমাণিত?

  • প্রসেসগুলো যেকোনো সময়ে আসতে পারে, preemption নেই
  • সব প্রসেস t = ০-তে উপস্থিত, burst জানা, একটি CPU, switch-এর খরচ শূন্য
  • Burst অজানা, exponential averaging দিয়ে অনুমান করা হয়
  • লক্ষ্য সবচেয়ে দুর্ভাগা প্রসেসের অপেক্ষা কমানো

নিচের কোন নীতিতে একটি লম্বা প্রসেসের starvation হতে পারে, যদি ছোট প্রসেস অবিরাম আসতে থাকে?

  • শুধু FCFS
  • HRRN আর FCFS
  • SJF আর SRTF
  • কোনোটিতেই নয়

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. TAT = CT − AT, WT = TAT − BT, RT = প্রথম শুরু − AT; Gantt chart t = ০ থেকে, idle ফাঁকসহ। Non-preemptive নীতিতে RT = WT.
  2. FCFS: FIFO, non-preemptive, সরল, starvation নেই — কিন্তু convoy effect (২৪/৩/৩-এ গড় WT ১৭ বনাম ৩)।
  3. SJF: এসে যাওয়াদের মধ্যে ছোট burst আগে, tie-তে FCFS; সবাই t = ০-তে থাকলে গড় WT সর্বনিম্ন (exchange argument). SRTF: preemptive রূপ, বাকি সময়ের তুলনা, গড় আরও কম, switch বেশি।
  4. SJF/SRTF-এ starvation সম্ভব আর burst অজানা; অনুমান τ(n+1) = α·t(n) + (1 − α)·τ(n) — α = 0 শুধু ইতিহাস, α = 1 শুধু সর্বশেষ।
  5. HRRN: R = (W + S)/S সবচেয়ে বেশি যার; ছোট কাজের সুবিধা রেখেও starvation-মুক্ত। তুলনার প্রশ্নে মাপকাঠি বলুন — গড়ে SRTF সেরা, সর্বোচ্চ turnaround-এ FCFS.

পরের পর্বে: Burst না জেনেও সবাইকে দ্রুত সাড়া দেওয়ার উপায় — Round Robin আর তার time quantum, আর অগ্রাধিকারভিত্তিক scheduling, যেখানে starvation ঠেকাতে আবার আসবে aging.

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান