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

Deadlock avoidance আর detection — Banker's algorithm

Deadlock Avoidance and Detection — Safe State, Banker's Algorithm, Wait-for Graph and Recovery

Taught in Bangla, this lesson compares prevention, avoidance and detection, then explains safe states and safe sequences, claim edges for single-instance resources, the Banker's algorithm with its Available, Max, Allocation and Need tables, the safety and resource-request algorithms with worked examples, deadlock detection for multiple instances, recovery by termination or preemption, and why Banker's is rare in real operating systems.

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

  • Safe state আর safe sequence-এর সংজ্ঞা, safe হলে কেন deadlock অসম্ভব তার প্রমাণ, আর unsafe অবস্থা কেন deadlock নয় — সংখ্যাসহ উদাহরণে
  • এক instance-এর resource-এ claim edge-সহ resource-allocation graph দিয়ে avoidance
  • Banker's algorithm-এর চার টেবিল, safety algorithm আর resource-request algorithm — পাঠ্যবইয়ের ৫ প্রসেস, ৩ ধরনের resource-এর পূর্ণ উদাহরণ, তিনটি অনুরোধসহ
  • Deadlock detection — wait-for graph আর একাধিক instance-এর algorithm, কত ঘন ঘন চালানো উচিত, আর recovery-র উপায়
  • Banker's কেন সাধারণ OS-এ চলে না, আর database, kernel ও ক্লাউডে এই ধারণা কোথায় দেখা যায়

১ · প্রতিরোধ, এড়ানো, নাকি খুঁজে বের করা

পর্ব ২৭-এ দেখেছেন, deadlock হতে চারটি শর্ত একসাথে লাগে — mutual exclusion, hold and wait, no preemption আর circular wait. সেখানে prevention-এর কৌশলও দেখেছেন: নকশা দিয়েই চারটির অন্তত একটি অসম্ভব করে দেওয়া, যেমন সব lock একটি নির্দিষ্ট ক্রমে নেওয়া। Prevention নিরাপদ, কিন্তু প্রায়ই অপচয়ী — resource অকারণে আটকে থাকে, বা প্রোগ্রাম লেখা কঠিন হয়।

এই পর্বে বাকি দুটি পথ। Deadlock avoidance-এ চারটি শর্ত ভাঙা হয় না; বদলে OS প্রতিটি অনুরোধের সময় হিসাব করে, এখন দিলে ভবিষ্যতে আটকে যাওয়ার ঝুঁকি আছে কি না। এর জন্য আগাম তথ্য লাগে — প্রতিটি প্রসেস সর্বোচ্চ কত চাইবে। আর deadlock detection-এ OS কিছুই আটকায় না; deadlock হতে দেয়, মাঝে মাঝে খোঁজে, পেলে recovery করে।

পাড়ার একটি ছোট সমবায় সমিতির কথা ভাবুন। তহবিলে আছে ১২ লাখ টাকা। তিনজন সদস্য ব্যবসার জন্য ঋণ-সীমা (credit line) পেয়েছেন — একজনের সর্বোচ্চ ১০ লাখ, একজনের ৪ লাখ, একজনের ৯ লাখ। কেউ একবারে পুরো টাকা নেন না; কিস্তিতে নেন, আর ব্যবসা শেষ হলে পুরো টাকা ফেরত দেন। কিন্তু সীমা পর্যন্ত না পেলে কারও ব্যবসা শেষ হয় না। সমিতির ক্যাশিয়ার প্রতিটি কিস্তি দেওয়ার আগে ভাবেন: “এটি দিলে কি এমন একটি ক্রম থাকবে, যাতে প্রত্যেককে একে একে তাঁর বাকি সীমা দিয়ে কাজ শেষ করানো যায়?” থাকলে দেন, না থাকলে বলেন “একটু অপেক্ষা করুন”। Edsger Dijkstra ঠিক এই ব্যাংকারের গল্প দিয়েই algorithm-টি বুঝিয়েছিলেন — নামও সেখান থেকে।

কৌশলকখন কাজ করেকী লাগেদাম
Prevention (পর্ব ২৭)নকশার সময়নিয়ম, যেমন lock orderingresource-এর কম ব্যবহার, নমনীয়তা কম
Avoidance (এই পর্ব)প্রতিটি অনুরোধেপ্রতিটি প্রসেসের সর্বোচ্চ চাহিদা আগে থেকেপ্রতি অনুরোধে হিসাব; অনেক সময় অকারণ অপেক্ষা
Detection ও recovery (এই পর্ব)deadlock হওয়ার পরএখন কে কী ধরে আছে, কী চাইছেমাঝে মাঝে হিসাব; উদ্ধারে কাজ নষ্ট
উপেক্ষা (“ostrich algorithm”)কখনো নাকিছুই নাকালেভদ্রে আটকে গেলে মানুষকে restart করতে হয়

শেষ সারিটি ঠাট্টা মনে হলেও Linux, Windows আর macOS সাধারণ প্রোগ্রামের lock আর resource-এর ক্ষেত্রে মূলত এটিই করে — প্রোগ্রামের deadlock প্রোগ্রামারের দায়িত্ব। তাহলে Banker's শিখে লাভ কী? তিনটি: পরীক্ষায় এটি সবচেয়ে বেশি আসা numerical-গুলোর একটি; “safe state” চিন্তাটি database, ক্লাউড আর embedded সিস্টেমে বারবার ফিরে আসে; আর detection algorithm বাস্তবে সত্যিই চলে — প্রায় প্রতিটি বড় database-এ।

২ · Safe state আর safe sequence

ধরুন সিস্টেমে n টি প্রসেস P0 … Pn−1. প্রতিটি প্রসেস শুরুতে ঘোষণা করেছে সে সর্বোচ্চ কত resource চাইবে, আর একটি প্রসেস তার সর্বোচ্চ পেয়ে গেলে সীমিত সময়ে কাজ শেষ করে সব ফেরত দেয়।

সংজ্ঞা

Safe sequence: প্রসেসগুলোর একটি ক্রম <Pa, Pb, …>, যেখানে প্রতিটি Pi-এর বাকি চাহিদা (Need) মেটানো যায় এখনকার খালি resource + তার আগে ক্রমে থাকা সব প্রসেসের ধরে রাখা resource দিয়ে। Safe state: যে অবস্থায় অন্তত একটি safe sequence আছে। Unsafe state: যে অবস্থায় কোনো safe sequence নেই।

অর্থাৎ safe sequence হলো একটি “পরিকল্পনা”: প্রথম জনকে এখনকার খালি resource দিয়েই শেষ করানো যায়; সে শেষ করে সব ফেরত দিলে দ্বিতীয় জনের জন্য যথেষ্ট হয়; এভাবে শেষ পর্যন্ত।

Safe ⇒ deadlock নেই — ছোট প্রমাণ

দাবি: সিস্টেম safe state-এ থাকলে, OS সবচেয়ে খারাপ পরিস্থিতিতেও (প্রত্যেকে নিজের পুরো সর্বোচ্চ চাইলেও) সবাইকে শেষ করাতে পারে, তাই deadlock অনিবার্য নয়। প্রমাণ (induction): safe sequence <P1, …, Pn> ধরুন। P1-এর Need ≤ Available, তাই সে যা-ই চাক, OS তা দিতে পারে; সে সীমিত সময়ে শেষ করে নিজের সব ফেরত দেয়। এখন ধরুন P1 … Pk−1 শেষ হয়েছে। তখন খালি resource = Available + তাদের সবার Allocation, আর safe sequence-এর সংজ্ঞা অনুযায়ী Pk-এর Need এর মধ্যে আঁটে। তাই Pk-ও শেষ হয়। k = n পর্যন্ত চললে সবাই শেষ — কেউ চিরকাল অপেক্ষায় থাকে না। Banker's algorithm প্রতিটি অনুরোধ শুধু তখনই মঞ্জুর করে, যখন নতুন অবস্থাও safe; ফলে সিস্টেম কখনো safe-এর বাইরে যায় না, আর deadlock কখনো ঘটে না। ∎

Unsafe ≠ deadlock — কেন

পাঠ্যবইয়ের একটি পরিচিত উদাহরণ দেখুন (সমিতির গল্পটিই, লাখ টাকার বদলে ১২টি tape drive). একটিমাত্র ধরনের resource, মোট ১২টি।

প্রসেসMaxএখন ধরে আছেNeed
P0১০৫৫
P1৪২২
P2৯২৭
খালি (Available) = ১২ − ৯ = ৩

এই অবস্থা safe: <P1, P0, P2>. P1-এর Need ২ ≤ ৩, সে শেষ করলে খালি হয় ৫; P0-এর Need ৫ ≤ ৫, শেষে খালি ১০; P2-এর Need ৭ ≤ ১০, শেষে ১২. এখন ধরুন P2 আরও একটি drive চাইল, আর OS না ভেবে দিয়ে দিল। P2 ধরে ৩, Need ৬, খালি ২. P1 এখনো শেষ করতে পারে (খালি হয় ৪), কিন্তু তারপর P0-এর দরকার ৫, P2-এর ৬ — কেউই ৪ দিয়ে হয় না। কোনো safe sequence নেই: অবস্থাটি unsafe.

কিন্তু এটি কি deadlock? না, এখনো নয়। Max হলো “সবচেয়ে খারাপ হলে এতটা লাগতে পারে”-র ঘোষণা, বাধ্যবাধকতা নয়। P0 হয়তো আর একটিও drive না চেয়ে নিজের ৫টি দিয়েই কাজ শেষ করবে; তাহলে খালি হবে ৯, আর P2-এর ৬ মিটে যাবে। Deadlock হবে কেবল যদি P0 আর P2 দুজনেই সত্যিই নিজেদের পুরো বাকি চাহিদা চায়, আর সেই ক্রমে চায়। Unsafe মানে OS আর নিশ্চয়তা দিতে পারছে না — ভবিষ্যৎ অনুরোধের ওপর ফল নির্ভর করছে। তাই সম্পর্কটি তিন স্তরের:

deadlock ⊂ unsafe · safe ∩ deadlock = ∅
Safe হলে deadlock অসম্ভব · unsafe হলে সম্ভব, নিশ্চিত নয়

Avoidance তাই ইচ্ছা করে সতর্ক: unsafe অবস্থায় ঢোকার আগেই থেমে যায়, যদিও অনেক unsafe অবস্থা থেকে বাস্তবে কিছুই খারাপ হতো না। এই সতর্কতার দাম — কখনো খালি resource থাকা সত্ত্বেও প্রসেসকে অপেক্ষা করানো — অংশ ৫-এ সংখ্যাসহ দেখবেন।

৩ · এক instance-এর resource — claim edge-সহ graph

প্রতিটি resource type-এর যদি একটিমাত্র instance থাকে (একটি printer, একটি lock), তাহলে Banker's-এর টেবিল লাগে না; পর্ব ২৭-এর resource-allocation graph-এ একটি নতুন ধরনের edge যোগ করলেই চলে — claim edge.

  • Claim edge Pi ⇢ Rj (ভাঙা দাগ): “Pi ভবিষ্যতে Rj চাইতে পারে।” প্রসেস শুরুর আগেই সব claim edge ঘোষণা করতে হয়।
  • অনুরোধ এলে: claim edge হয়ে যায় request edge Pi → Rj.
  • মঞ্জুর হলে: request edge উল্টে assignment edge Rj → Pi. ছেড়ে দিলে আবার claim edge.
  • নিয়ম: অনুরোধটি মঞ্জুর করা হয় কেবল যদি request edge-কে assignment edge-এ বদলালে graph-এ (claim edge-সহ) কোনো cycle না হয়। Cycle হলে অবস্থা unsafe, তাই resource খালি থাকলেও প্রসেস অপেক্ষা করে।

Cycle খোঁজার খরচ graph-এর edge-সংখ্যার সমানুপাতিক; n টি প্রসেসে edge সর্বোচ্চ n²-এর ক্রমে, তাই পাঠ্যবই একে O(n²) বলে। নিচে দুটি প্রসেস আর দুটি resource দিয়ে পুরো ঘটনাটি দেখুন।

1 declare: P1 may use R1, R2; P2 may use R1, R22 P1 requests R1  -> no cycle, GRANT3 P2 requests R1  -> R1 busy, P2 waits4 P2 requests R2  -> cycle with claim, DENY5 P1 requests R2  -> no cycle, GRANT6 P1 finishes     -> P2 gets R1, then R2
Graph-এর edge (ধাপের শেষে)
Assignment
Request
Claim (ভাঙা দাগ)
সিদ্ধান্ত
  1. শুরু: চারটি claim edge. P1 আর P2 দুজনেই ঘোষণা করেছে, তারা R1 আর R2 দুটিই ব্যবহার করতে পারে। এখনো কেউ কিছু ধরে নেই।
  2. P1 চাইল R1. R1 খালি। “দিয়েছি ধরে নিই”: P1⇢R1 বদলে R1→P1. P1-এর বাইরের দিকে শুধু P1⇢R2, আর R2 থেকে কোনো edge বেরোয় না — cycle নেই, মঞ্জুর।
  3. P2 চাইল R1. R1 এখন P1-এর কাছে, তাই প্রশ্নই ওঠে না — P2→R1 request edge নিয়ে P2 অপেক্ষা করে। এটি সাধারণ অপেক্ষা, avoidance-এর সিদ্ধান্ত নয়।
  4. P2 চাইল R2 — R2 খালি, তবু না। ধরে নিই R2→P2. তখন P1 ⇢ R2 → P2 → R1 → P1 — claim edge-সহ একটি cycle. মানে: P1 পরে R2 চাইলে দুজন দুজনের জন্য অপেক্ষা করবে। অবস্থাটি unsafe, তাই অনুরোধ প্রত্যাখ্যাত; P2⇢R2 claim হিসেবেই থাকে। (পাঠ্যবইয়ের পরিচিত ছবির মতো এখানে ধরে নেওয়া হয়েছে, R1-এর অপেক্ষায় থাকা অবস্থায়ও P2 R2-এর অনুরোধ জানাতে পারে।)
  5. P1 চাইল R2. ধরে নিই R2→P1. এখন P1 দুটিই ধরে আছে, তার কোনো বাইরের edge নেই, তাই কোনো cycle P1-এর ভেতর দিয়ে যেতে পারে না — মঞ্জুর। ধাপ ৪-এ R2 আটকে না রাখলে এখানে deadlock হতো।
  6. P1 শেষ, সব ফেরত। P2 আগে R1 পায় (অপেক্ষার অনুরোধ), তারপর R2 চাইলে কোনো cycle থাকে না, তাই পায়, আর শেষ করে। ধাপ ৪-এর “অকারণ” অপেক্ষাটিই avoidance-এর দাম।

এই পদ্ধতি একাধিক instance-এ কাজ করে না: সেখানে cycle থাকলেও deadlock না-ও হতে পারে (পর্ব ২৭), তাই cycle-পরীক্ষা খুব কড়া হয়ে যায়। সেখানেই লাগে Banker's.

৪ · Banker's algorithm — চার টেবিল আর safety algorithm

Edsger W. Dijkstra ১৯৬০-এর দশকের মাঝামাঝি (সাধারণত ১৯৬৫ সালের আশেপাশে বলা হয়) নেদারল্যান্ডসে THE multiprogramming সিস্টেমের কাজের সময় এই algorithm বর্ণনা করেন; তাঁর হাতে লেখা নোটের সিরিজ (EWD) থেকে এটি ছড়ায়। মূল রূপটি ছিল একটিমাত্র ধরনের resource-এর জন্য — ব্যাংকারের টাকার গল্প। একাধিক ধরনের resource-এ এর সাধারণীকরণ নিয়ে পরে, ১৯৬০-এর দশকের শেষ দিকে, A. N. Habermann-সহ অন্যরা কাজ করেন। Deadlock-এর চার শর্তের পরিচিত তালিকা (Coffman ও সহকর্মীরা, ১৯৭১) এসেছে এর পরে। তারিখ নিয়ে বিভিন্ন সূত্রে সামান্য পার্থক্য আছে; মূল কৃতিত্ব নিয়ে নেই।

ধরুন n টি প্রসেস আর m ধরনের resource. Banker's চারটি ডেটা স্ট্রাকচার রাখে:

নামআকারঅর্থ
AvailablemAvailable[j] = k মানে Rj-এর k টি instance এখন খালি
Maxn × mMax[i][j] = Pi সর্বোচ্চ কতটি Rj চাইতে পারে (শুরুতে ঘোষিত)
Allocationn × mAllocation[i][j] = Pi এখন কতটি Rj ধরে আছে
Needn × mNeed[i][j] = Max[i][j] − Allocation[i][j], অর্থাৎ আর কত চাইতে পারে

দুটি ভেক্টর তুলনার নিয়ম: X ≤ Y মানে প্রতিটি j-এর জন্য X[j] ≤ Y[j]. যেমন (১, ২, ২) ≤ (৩, ৩, ২) সত্য, কিন্তু (৭, ৪, ৩) ≤ (৫, ৩, ২) মিথ্যা — একটি উপাদানও বড় হলে মিথ্যা।

Safety algorithm

1. Work = Available            // vector of length m
   Finish[i] = false for every i
2. find an i with Finish[i] == false and Need[i] <= Work
   (if several qualify, this course picks the LOWEST index)
   if no such i exists, go to step 4
3. Work = Work + Allocation[i] // Pi finishes and returns everything
   Finish[i] = true; append Pi to the sequence; go to step 2
4. if Finish[i] == true for all i: SAFE (the sequence is a safe sequence)
   else: UNSAFE

খরচ: ধাপ ২ প্রতিবার সর্বোচ্চ n টি প্রসেস দেখে, প্রতিটিতে m টি তুলনা; আর ধাপ ২–৩ সর্বোচ্চ n বার ঘোরে। মোট O(m·n²). পরীক্ষায় এই সংখ্যাটি প্রায়ই চাওয়া হয়।

প্রার্থী একাধিক হলে কাকে আগে নেব — উত্তর কি বদলায়? Safe sequence বদলাতে পারে, কিন্তু “safe কি না” বদলায় না। কারণ: কোনো প্রসেস শেষ হলে Work শুধু বাড়ে, কখনো কমে না। তাই এখন যে প্রসেসের Need ≤ Work, পরেও সে যোগ্য থাকবে। কাউকে আগে বেছে নিলে অন্য কারও সুযোগ নষ্ট হয় না — যেকোনো যোগ্যকে বেছে নেওয়ার লোভী (greedy) পদ্ধতি safe sequence থাকলে একটি খুঁজে পাবেই। এই পর্বে নিয়ম: প্রতিবার ধাপ ২-এ সবচেয়ে ছোট index-এর যোগ্য প্রসেস. আপনার বই বা প্রশ্নে অন্য নিয়ম থাকলে (যেমন P0 থেকে বৃত্তাকারে ঘুরে চলা) ক্রম আলাদা হবে, কিন্তু সেটিও সঠিক।

পাঠ্যবইয়ের উদাহরণ

প্রশ্ন: পাঁচটি প্রসেস P0–P4, তিন ধরনের resource — A-এর মোট ১০টি, B-এর ৫টি, C-এর ৭টি। এই মুহূর্তের অবস্থা:

প্রসেসAllocation (A B C)Max (A B C)Need = Max − Allocation
P00 1 07 5 37 4 3
P12 0 03 2 21 2 2
P23 0 29 0 26 0 0
P32 1 12 2 20 1 1
P40 0 24 3 34 3 1
মোট Allocation = (৭, ২, ৫), তাই Available = (১০, ৫, ৭) − (৭, ২, ৫) = (৩, ৩, ২)

সিস্টেম কি safe? নিচে safety algorithm ধাপে ধাপে চলছে।

1 Work = Available = (3,3,2)2 P0? 7,4,3 no · P1: 1,2,2 <= 3,3,23 P0? no · P2? 6>5 no · P3: 0,1,1 ok4 P0: 7,4,3 <= 7,4,3 ok5 P2: 6,0,0 <= 7,5,3 ok6 P4: 4,3,1 <= 10,5,5 ok -> SAFE
অবস্থা (ধাপের শেষে)
Work (A, B, C)
Finish P0…P4
এই ধাপে বাছাই
Sequence
  1. শুরু। Work = Available = (৩, ৩, ২), সবার Finish = false. Need টেবিল আগেই Max − Allocation দিয়ে বের করা হয়েছে।
  2. P0 নয়, P1. P0-এর Need (৭, ৪, ৩)-এ A = ৭ > ৩, তাই বাদ। P1-এর (১, ২, ২) ≤ (৩, ৩, ২) — সে চলতে পারে। শেষে নিজের Allocation (২, ০, ০) ফেরত দেয়: Work = (৫, ৩, ২)।
  3. আবার শুরু থেকে খোঁজা: P3. P0-এর A = ৭ > ৫, বাদ; P2-এর A = ৬ > ৫, বাদ; P3-এর (০, ১, ১) ≤ (৫, ৩, ২) — চলে, ফেরত দেয় (২, ১, ১): Work = (৭, ৪, ৩)।
  4. এবার P0. P0-এর Need (৭, ৪, ৩) ঠিক Work-এর সমান — “≤” মানে সমানও চলবে। P0 ফেরত দেয় (০, ১, ০): Work = (৭, ৫, ৩)।
  5. P2. (৬, ০, ০) ≤ (৭, ৫, ৩). ফেরত (৩, ০, ২): Work = (১০, ৫, ৫)।
  6. P4, তারপর সবাই শেষ। (৪, ৩, ১) ≤ (১০, ৫, ৫). ফেরত (০, ০, ২): Work = (১০, ৫, ৭) — ঠিক মোট resource, যা একটি ভালো যাচাই। সবার Finish = true, তাই safe, sequence <P1, P3, P0, P2, P4>. পাঠ্যবইয়ে দেওয়া <P1, P3, P4, P2, P0>-ও সঠিক — ভিন্ন বাছাই-নিয়মের ফল।

নিচের simulator Need বের করে, প্রতিটি ধাপে Work দেখায়, তারপর তিনটি অনুরোধ একে একে বিচার করে (অনুরোধের নিয়ম অংশ ৫-এ)। সংখ্যা বদলে নিজের বইয়ের অনুশীলনী যাচাই করতে পারেন — তবে আগে হাতে করুন।

// Banker's algorithm: Need, safety check (lowest index first), request evaluation.
const max = [[7,5,3], [3,2,2], [9,0,2], [2,2,2], [4,3,3]];
let alloc = [[0,1,0], [2,0,0], [3,0,2], [2,1,1], [0,0,2]];
let avail = [3,3,2];
const fits = (a, b) => a.every((x, j) => x <= b[j]);           // a <= b, element by element
const needOf = al => al.map((row, i) => row.map((x, j) => max[i][j] - x));
function safe(av, al, show) {
  const need = needOf(al), work = [...av], fin = al.map(() => false), seq = [];
  for (let k = 0; k < al.length; k++) {
    const i = fin.findIndex((f, p) => !f && fits(need[p], work)); // lowest index first
    if (i < 0) break;
    const before = work.join(",");
    al[i].forEach((x, j) => work[j] += x); fin[i] = true; seq.push("P" + i);
    if (show) console.log("  P" + i + ": Need " + need[i] + " <= Work " + before + " -> Work " + work);
  }
  return fin.every(f => f) ? seq : null;
}
function request(p, req) {
  const tag = "P" + p + " requests (" + req + "): ";
  if (!fits(req, needOf(alloc)[p])) return console.log(tag + "ERROR, more than its Need");
  if (!fits(req, avail)) return console.log(tag + "WAIT, Available is only " + avail);
  const av2 = avail.map((x, j) => x - req[j]);                    // pretend to allocate
  const al2 = alloc.map((r, i) => i === p ? r.map((x, j) => x + req[j]) : [...r]);
  const seq = safe(av2, al2, false);
  if (!seq) return console.log(tag + "DENIED, new state unsafe -> roll back, P" + p + " waits");
  avail = av2; alloc = al2;
  console.log(tag + "GRANTED, Available = " + avail + ", safe sequence " + seq.join(" "));
}
console.log("Need: " + needOf(alloc).map((r, i) => "P" + i + "=" + r).join("  "));
console.log("Safety check, Work starts at " + avail);
console.log("Safe sequence: " + safe(avail, alloc, true).join(" "));
request(1, [1,0,2]);
request(4, [3,3,0]);
request(0, [0,2,0]);

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

Need: P0=7,4,3  P1=1,2,2  P2=6,0,0  P3=0,1,1  P4=4,3,1
Safety check, Work starts at 3,3,2
  P1: Need 1,2,2 <= Work 3,3,2 -> Work 5,3,2
  P3: Need 0,1,1 <= Work 5,3,2 -> Work 7,4,3
  P0: Need 7,4,3 <= Work 7,4,3 -> Work 7,5,3
  P2: Need 6,0,0 <= Work 7,5,3 -> Work 10,5,5
  P4: Need 4,3,1 <= Work 10,5,5 -> Work 10,5,7
Safe sequence: P1 P3 P0 P2 P4
P1 requests (1,0,2): GRANTED, Available = 2,3,0, safe sequence P1 P3 P0 P2 P4
P4 requests (3,3,0): WAIT, Available is only 2,3,0
P0 requests (0,2,0): DENIED, new state unsafe -> roll back, P0 waits

প্রথম সাত লাইন ঠিক উপরের stepper: Need টেবিল, তারপর প্রতিটি ধাপে কে চলল আর Work কত হলো, শেষে Work = (১০, ৫, ৭) = মোট resource. findIndex প্রতিবার ০ থেকে খোঁজে — এটিই “সবচেয়ে ছোট index আগে” নিয়ম। শেষ তিন লাইন তিনটি অনুরোধের রায়: P1-এরটি মঞ্জুর, P4-কে অপেক্ষা করতে হবে কারণ resource-ই নেই, আর P0-এরটি resource থাকা সত্ত্বেও প্রত্যাখ্যাত। কেন — পরের অংশে। খেয়াল করুন, অনুরোধগুলো ক্রমে চলে: P4 আর P0-এর বিচার হয় P1-এর অনুরোধ মঞ্জুর হওয়ার পরের অবস্থায়।

একই safety algorithm C-তে, যেভাবে পরীক্ষার খাতায় বা ইন্টারভিউতে লিখতে বলা হয়। এখানে Need আলাদা টেবিলে না রেখে max − alloc সরাসরি হিসাব করা হয়েছে:

#include <stdio.h>
#include <stdbool.h>
#define N 5   /* processes */
#define M 3   /* resource types A, B, C */

int alloc[N][M] = {{0,1,0}, {2,0,0}, {3,0,2}, {2,1,1}, {0,0,2}};
int max[N][M]   = {{7,5,3}, {3,2,2}, {9,0,2}, {2,2,2}, {4,3,3}};
int avail[M]    = {3, 3, 2};

int main(void) {
    int work[M], seq[N], count = 0;
    bool finish[N] = {false};
    for (int j = 0; j < M; j++) work[j] = avail[j];
    while (count < N) {
        int found = -1;
        for (int i = 0; i < N && found < 0; i++) {     /* lowest index first */
            if (finish[i]) continue;
            bool ok = true;
            for (int j = 0; j < M; j++)
                if (max[i][j] - alloc[i][j] > work[j]) ok = false;   /* Need > Work */
            if (ok) found = i;
        }
        if (found < 0) break;                            /* nobody can finish */
        for (int j = 0; j < M; j++) work[j] += alloc[found][j];
        finish[found] = true;
        seq[count++] = found;
    }
    if (count < N) { printf("UNSAFE: only %d of %d can finish\n", count, N); return 0; }
    printf("SAFE, sequence:");
    for (int k = 0; k < N; k++) printf(" P%d", seq[k]);
    printf("\nWork at the end: %d %d %d\n", work[0], work[1], work[2]);
    return 0;
}

আউটপুট:

SAFE, sequence: P1 P3 P0 P2 P4
Work at the end: 10 5 7

JS simulator-এর সাথে হুবহু এক ক্রম, কারণ ভেতরের for লুপ প্রতিবার i = 0 থেকে শুরু হয় আর প্রথম যোগ্যকে পেলেই থামে। তিনটি নেস্টেড লুপ — বাইরে সর্বোচ্চ n বার, মাঝে n টি প্রসেস, ভেতরে m টি resource — চোখের সামনেই O(m·n²). count < N হলে কয়েকজন কখনো যোগ্য হয়নি: সেটি unsafe.

৫ · Resource-request algorithm — দেব, নাকি অপেক্ষা করাব

প্রসেস Pi একটি অনুরোধ-ভেক্টর Requesti নিয়ে এলে OS চারটি ধাপে সিদ্ধান্ত নেয় — এটিই resource-request algorithm:

  1. Requesti ≤ Needi? না হলে এটি ভুল (error) — প্রসেস নিজের ঘোষিত সর্বোচ্চের বেশি চাইছে। সাধারণত প্রসেসটিকে বন্ধ করা বা অনুরোধ বাতিল করা হয়।
  2. Requesti ≤ Available? না হলে resource-ই নেই — Pi অপেক্ষা করে।
  3. দিয়েছি ধরে নেওয়া (pretend): Available −= Requesti; Allocationi += Requesti; Needi −= Requesti.
  4. Safety algorithm চালানো: নতুন অবস্থা safe হলে অনুরোধ মঞ্জুর। Unsafe হলে ধাপ ৩-এর বদল ফিরিয়ে নেওয়া (roll back), আর Pi অপেক্ষা করে।

এবার পাঠ্যবইয়ের তিনটি অনুরোধ, একটির পর একটি: প্রথমে P1 চায় (১, ০, ২); তারপর P4 চায় (৩, ৩, ০); তারপর P0 চায় (০, ২, ০)।

1 P1 asks (1,0,2): <= Need 1,2,2, <= Avail 3,3,22 pretend: Avail 2,3,0 · Need P1 0,2,03 safety: P1 P3 P0 P2 P4 -> GRANTED4 P4 asks (3,3,0): A 3 > Avail 2 -> WAIT5 P0 asks (0,2,0): checks pass; pretend6 safety: nobody fits 2,1,0 -> DENIED
অবস্থা (ধাপের শেষে)
Available
অনুরোধকারীর Allocation
অনুরোধকারীর Need
রায়
  1. P1 চায় (১, ০, ২) — প্রথম দুই পরীক্ষা। P1-এর Need (১, ২, ২)-এর মধ্যে আছে, আর Available (৩, ৩, ২)-এরও মধ্যে। অর্থাৎ অনুরোধটি বৈধ, আর resource আছে।
  2. দিয়েছি ধরে নিই। Available = (২, ৩, ০), P1-এর Allocation = (৩, ০, ২), Need = (০, ২, ০)। এখনো কিছু চূড়ান্ত নয়।
  3. Safety: safe, তাই মঞ্জুর। Work (২, ৩, ০) থেকে P1 (Need ০, ২, ০) চলে → (৫, ৩, ২) → P3 → (৭, ৪, ৩) → P0 → (৭, ৫, ৩) → P2 → (১০, ৫, ৫) → P4 → (১০, ৫, ৭)। Sequence <P1, P3, P0, P2, P4>, তাই অনুরোধ স্থায়ী হয়।
  4. P4 চায় (৩, ৩, ০) — resource নেই। Need (৪, ৩, ১)-এর মধ্যে আছে, কিন্তু A-এর ৩টি চাই, খালি মাত্র ২টি। Safety চালানোর প্রশ্নই নেই — P4 অপেক্ষা করে। অবস্থা বদলায় না।
  5. P0 চায় (০, ২, ০) — দুই পরীক্ষাই পাস। (০, ২, ০) ≤ Need (৭, ৪, ৩), আর ≤ Available (২, ৩, ০)। ধরে নিই: Available = (২, ১, ০), P0-এর Allocation (০, ৩, ০), Need (৭, ২, ৩)।
  6. Safety: কেউই শুরু করতে পারে না। Work = (২, ১, ০)। P0-এর A ৭ > ২; P1-এর B ২ > ১; P2-এর A ৬ > ২; P3-এর C ১ > ০; P4-এর A ৪ > ২। প্রথম ধাপেই কেউ নেই, তাই unsafe. বদল ফিরিয়ে নেওয়া হয়: Available আবার (২, ৩, ০), P0-এর Allocation (০, ১, ০) — আর P0 অপেক্ষা করে, যদিও চাওয়া B দুটি খালি পড়ে আছে।

একটি সূক্ষ্ম বিষয়: একই P0 (০, ২, ০) অনুরোধ যদি শুরুর অবস্থায় (P1-এর অনুরোধের আগে, Available = ৩, ৩, ২) আসত, তাহলে ধরে নেওয়ার পর Available হতো (৩, ১, ২), আর simulator-এর একই নিয়মে safe sequence মেলে <P3, P1, P0, P2, P4> — মঞ্জুর। অর্থাৎ একটি অনুরোধের রায় নির্ভর করে কখন এল তার ওপর; পরীক্ষায় প্রশ্নের অনুরোধগুলো আলাদা আলাদা (শুরুর অবস্থা থেকে) না পরপর — সেটি আগে স্পষ্ট পড়ে নিন।

খাতায় সবচেয়ে বেশি হওয়া ভুল: (১) Need-এর বদলে Max-কে Work-এর সাথে তুলনা করা; (২) কোনো প্রসেস শেষ হলে Work-এ তার Need যোগ করা — যোগ হবে তার Allocation, কারণ সে যা ধরে ছিল তাই ফেরত দেয় (Need-টুকু সে পেয়েছিল Work থেকেই, তাও ফেরত দেয়, তাই নিট লাভ Allocation); (৩) অনুরোধ মঞ্জুরের পর Need কমাতে ভুলে যাওয়া; (৪) শেষে Work মোট resource-এর সমান হলো কি না না মেলানো।

৬ · Deadlock detection — হতে দিয়ে খুঁজে বের করা

Avoidance-এর দুই শর্ত — সবার Max আগে জানা, আর প্রতিটি অনুরোধে হিসাব — বেশিরভাগ সিস্টেমে মেলে না। Detection-এর জন্য ভবিষ্যৎ জানা লাগে না, শুধু এখন কে কী ধরে আছে আর কে কীসের জন্য অপেক্ষা করছে।

এক instance: wait-for graph

প্রতিটি resource-এর একটি instance হলে resource-allocation graph থেকে resource-এর node গুলো মুছে দিন: Pi → R → Pj (Pi চায় R, যা Pj-এর কাছে) হয়ে যায় Pi → Pj, অর্থাৎ “Pi অপেক্ষা করছে Pj-এর জন্য।” এটিই wait-for graph. এখানে cycle থাকা মানেই deadlock, আর cycle-এর প্রসেসগুলোই আটকে আছে। খোঁজার খরচ O(n²) (DFS দিয়ে cycle খোঁজা, পর্ব ২৭ আর DSA-র graph পর্ব)।

resource-allocation graph            wait-for graph
P1 -> R1 -> P2                         P1 -> P2
P2 -> R3 -> P3   (R = one instance)    P2 -> P3
P3 -> R2 -> P1                         P3 -> P1     cycle P1 -> P2 -> P3 -> P1: DEADLOCK
P4 -> R1 -> P2                         P4 -> P2     P4 waits, but is NOT in the cycle

লক্ষ করুন P4: সে আটকে থাকা P2-এর জন্য অপেক্ষা করছে, তাই কার্যত সেও থেমে আছে, কিন্তু cycle-এ নেই। Cycle ভাঙলে (যেমন P3 বন্ধ করলে) P2 এগোবে, আর P4-ও পরে পাবে।

একাধিক instance: detection algorithm

এখানে Banker's-এর মতো টেবিল লাগে, কিন্তু Max বা Need নেই — আছে Request: Pi এই মুহূর্তে কীসের জন্য আটকে আছে।

1. Work = Available
   Finish[i] = true  if Allocation[i] == 0   // holds nothing: cannot be part of a deadlock
   Finish[i] = false otherwise
2. find an i with Finish[i] == false and Request[i] <= Work   (lowest index first)
   if none, go to step 4
3. Work = Work + Allocation[i]; Finish[i] = true; go to step 2
4. every i with Finish[i] == false is DEADLOCKED

Safety algorithm-এর সাথে তিনটি পার্থক্য: Need-এর জায়গায় Request; কিছু না ধরে থাকা প্রসেস শুরু থেকেই Finish = true; আর ফলাফল শুধু “হ্যাঁ/না” নয় — কারা আটকে আছে তার তালিকা। খরচ একই, O(m·n²). ধাপ ৩-এ একটি আশাবাদী অনুমান আছে: যার Request মেটানো যায়, সে আর কিছু চাইবে না আর শেষ করবে। ভুল হলে ক্ষতি নেই — পরে সে আরও চাইলে পরের detection-এ ধরা পড়বে।

প্রশ্ন (পাঠ্যবইয়ের উদাহরণ): পাঁচটি প্রসেস P0–P4; A-এর মোট ৭টি, B-এর ২টি, C-এর ৬টি। Available = (০, ০, ০)।

প্রসেসAllocation (A B C)Request, অবস্থা ১Request, অবস্থা ২
P00 1 00 0 00 0 0
P12 0 02 0 22 0 2
P23 0 30 0 00 0 1
P32 1 11 0 01 0 0
P40 0 20 0 20 0 2

অবস্থা ১-এ সিস্টেমে deadlock নেই (simulator-এর ক্রম <P0, P2, P1, P3, P4>). অবস্থা ২-এ শুধু একটি বদল — P2 আরও একটি C চায়। এবার কী হয়, দেখুন।

1 Work = (0,0,0); nobody has zero Allocation2 P0: Request 0,0,0 <= Work -> Work 0,1,03 P1 2,0,2 · P2 0,0,1 · P3 1,0,0 · P4 0,0,2: no4 Finish false: P1 P2 P3 P4 -> DEADLOCKED5 recovery test: abort P1, Work 2,1,06 P3 -> P2 -> P4 can now finish
অবস্থা (ধাপের শেষে)
Work (A, B, C)
Finish P0…P4
সিদ্ধান্ত
  1. শুরু। Work = Available = (০, ০, ০)। সবাই কিছু না কিছু ধরে আছে, তাই শূন্য-Allocation নিয়মে কেউ আগেভাগে Finish = true পায় না।
  2. P0 মুক্ত। P0 কিছুই চাইছে না (০, ০, ০), তাই সে শেষ করতে পারে; ফেরত দেয় (০, ১, ০)। Work = (০, ১, ০)।
  3. আর কেউ এগোতে পারে না। P1-এর লাগবে A ২টি, P2-এর C ১টি, P3-এর A ১টি, P4-এর C ২টি — Work-এ A আর C দুটোই শূন্য। Algorithm থেমে যায়।
  4. রায়: P1, P2, P3, P4 deadlocked. অবস্থা ১-এ P2 কিছু চাইছিল না বলে সে শেষ করে (৩, ০, ৩) ফেরত দিতে পারত, আর সেটিই বাকিদের মুক্ত করত। মাত্র একটি C-এর অনুরোধ পুরো দলকে আটকে দিল।
  5. Recovery-র পরীক্ষা: P1 বন্ধ। একজন একজন করে বন্ধ করার কৌশলে ধরুন প্রথম শিকার P1 (অংশ ৭)। তার (২, ০, ০) ফেরত আসে: Work = (২, ১, ০)।
  6. বাকিরা মুক্ত। P3-এর (১, ০, ০) মেটে → (৪, ২, ১); তারপর P2-এর (০, ০, ১) → (৭, ২, ৪); তারপর P4 → (৭, ২, ৬)। একজনকে বন্ধ করাই যথেষ্ট ছিল। Simulator দিয়ে দেখা যায়, এই উদাহরণে P1–P4-এর যে কাউকে একা বন্ধ করলেই বাকিরা মুক্ত হয় — তাই কাকে বন্ধ করবেন, তা ঠিক হয় খরচ দিয়ে।
// Deadlock detection, several instances per resource type (A, B, C).
// Request = what each process is waiting for RIGHT NOW (not a declared maximum).
function detect(title, avail, alloc, req) {
  console.log(title + " -- Work starts at " + avail);
  const work = [...avail];
  const fin = alloc.map(r => r.every(x => x === 0));  // holds nothing -> cannot be in a deadlock
  for (;;) {
    const i = fin.findIndex((f, p) => !f && req[p].every((x, j) => x <= work[j])); // lowest index
    if (i < 0) break;
    alloc[i].forEach((x, j) => work[j] += x);          // assume Pi finishes and returns everything
    fin[i] = true;
    console.log("  P" + i + ": Request " + req[i] + " <= Work -> Work " + work);
  }
  const stuck = fin.map((f, i) => f ? null : "P" + i).filter(Boolean);
  console.log(stuck.length ? "  DEADLOCKED: " + stuck.join(", ") : "  no deadlock");
}
const alloc = [[0,1,0], [2,0,0], [3,0,3], [2,1,1], [0,0,2]];   // A = 7, B = 2, C = 6 in total
detect("Case 1", [0,0,0], alloc, [[0,0,0], [2,0,2], [0,0,0], [1,0,0], [0,0,2]]);
detect("Case 2: P2 asks one more C", [0,0,0], alloc,
       [[0,0,0], [2,0,2], [0,0,1], [1,0,0], [0,0,2]]);

আউটপুট:

Case 1 -- Work starts at 0,0,0
  P0: Request 0,0,0 <= Work -> Work 0,1,0
  P2: Request 0,0,0 <= Work -> Work 3,1,3
  P1: Request 2,0,2 <= Work -> Work 5,1,3
  P3: Request 1,0,0 <= Work -> Work 7,2,4
  P4: Request 0,0,2 <= Work -> Work 7,2,6
  no deadlock
Case 2: P2 asks one more C -- Work starts at 0,0,0
  P0: Request 0,0,0 <= Work -> Work 0,1,0
  DEADLOCKED: P1, P2, P3, P4

অবস্থা ১-এ ক্রম <P0, P2, P1, P3, P4>, আর শেষে Work = (৭, ২, ৬) = মোট resource — সবাই শেষ করতে পারে। পাঠ্যবইয়ে <P0, P2, P3, P1, P4> দেওয়া থাকে; সেটিও সঠিক, শুধু বাছাই-নিয়ম আলাদা। অবস্থা ২-এ P0-এর পরেই সব থেমে যায়, আর চারজনের তালিকা আসে। alloc এখানে বদলায় না (work আলাদা কপি), তাই একই টেবিল দুবার ব্যবহার করা নিরাপদ।

কখন, কত ঘন ঘন চালাবেন

দুটি প্রশ্নের ওপর নির্ভর করে: deadlock কত ঘন ঘন হয়, আর হলে কতগুলো প্রসেস ক্ষতিগ্রস্ত হয়।

  • প্রতিটি অনুরোধে, যা সঙ্গে সঙ্গে মেটানো যায় না: সবচেয়ে দ্রুত ধরা পড়ে, আর কোন অনুরোধটি cycle তৈরি করল তাও জানা যায় — কিন্তু খরচ সবচেয়ে বেশি।
  • নির্দিষ্ট সময় পরপর (যেমন কয়েক মিনিট বা ঘণ্টায় একবার), বা যখন CPU-র ব্যবহার অস্বাভাবিকভাবে কমে যায় (deadlock হলে প্রসেসগুলো ঘুমিয়ে থাকে, CPU অলস হয়) — পাঠ্যবই এর উদাহরণ হিসেবে ৪০%-এর নিচে নামার কথা বলে। খরচ কম, কিন্তু ততক্ষণে আটকে থাকা দল বড় হয়ে যেতে পারে, আর কে “দোষী” তা বলা কঠিন।
  • Database-এর বাস্তব উদাহরণ: PostgreSQL একটি lock-এর জন্য কিছুক্ষণ অপেক্ষার পর (ডিফল্ট deadlock_timeout ১ সেকেন্ড) তবেই wait-for graph পরীক্ষা করে — কারণ বেশিরভাগ অপেক্ষা deadlock নয়, আর প্রতিবার পরীক্ষা ব্যয়বহুল। MySQL-এর InnoDB ডিফল্টভাবে অপেক্ষা শুরু হতেই পরীক্ষা করে, আর খুব বেশি concurrency-তে এই পরীক্ষা বন্ধ করে শুধু lock-wait timeout-এর ওপর ভরসা করার সেটিংও রাখে। বিস্তারিত DBMS কোর্সের concurrency control পর্বে।

৭ · Recovery — বন্ধ করা, নাকি কেড়ে নেওয়া

Deadlock ধরা পড়লে দুটি পথ: প্রসেস বন্ধ করা, বা resource কেড়ে নেওয়া।

প্রসেস বন্ধ (termination)

সবাইকে একসাথে: নিশ্চিত আর দ্রুত, কিন্তু অনেক কাজ নষ্ট — হয়তো কেউ কয়েক ঘণ্টা হিসাব করছিল। একজন একজন করে: প্রতিবার একজনকে বন্ধ করে আবার detection চালানো, যতক্ষণ না deadlock ভাঙে। কম কাজ নষ্ট, কিন্তু প্রতিবার detection-এর খরচ।

ঝুঁকি: অর্ধেক লেখা ফাইল বা ডেটা

Resource কেড়ে নেওয়া (preemption)

কোনো প্রসেস থেকে resource নিয়ে অন্যকে দেওয়া। তিনটি প্রশ্ন: কার থেকে নেবেন (victim)? যার থেকে নেবেন, তাকে কোথায় ফেরাবেন (rollback — একদম শুরুতে, বা আগে সংরক্ষিত কোনো checkpoint-এ)? আর একই জন বারবার শিকার হলে কী হবে (starvation)?

শর্ত: প্রসেসের অবস্থা ফিরিয়ে আনা যায় এমন হতে হবে

কাকে বন্ধ করবেন বা কার থেকে নেবেন (victim selection) — পাঠ্যবইয়ের মাপকাঠিগুলো, যাদের লক্ষ্য মোট খরচ সর্বনিম্ন রাখা:

  • প্রসেসের priority কত
  • এ পর্যন্ত কতক্ষণ চলেছে, আর শেষ হতে আর কতক্ষণ লাগবে
  • কত resource আর কোন ধরনের ধরে আছে (সহজে কেড়ে নেওয়া যায় কি না)
  • শেষ করতে আর কত resource লাগবে
  • মোট কতগুলো প্রসেস বন্ধ করতে হবে
  • প্রসেসটি interactive না batch (ব্যবহারকারী বসে আছেন কি না)

Starvation ঠেকানো: খরচের হিসাব যদি প্রতিবার একই প্রসেসকে সবচেয়ে “সস্তা” দেখায়, সে বারবার rollback হবে আর কখনো শেষ করবে না। সমাধান — খরচের সূত্রে এ পর্যন্ত কতবার rollback হয়েছে সেই সংখ্যাটি যোগ করা, যাতে প্রতিবার শিকার হলে পরের বার তাকে বেছে নেওয়ার খরচ বাড়ে। এটি পর্ব ১৮-এর aging-এর আত্মীয়। Database-এ এটি খুব বাস্তব: deadlock-এ একটি transaction-কে বাতিল (abort) করে rollback করা হয়, আর অ্যাপ্লিকেশন আবার চেষ্টা করে। InnoDB সাধারণত “ছোট” transaction — কম row বদলেছে এমনটি — বাতিলের জন্য বেছে নেয়।

ঢাকার একটি সরু গলিতে দুই দিক থেকে দুটি রিকশা ঢুকে মুখোমুখি আটকে গেছে, পেছনে আরও কয়েকটি। Detection মানে কেউ একজন সামনে গিয়ে দেখলেন — হ্যাঁ, কেউ এগোতে পারছে না। Recovery মানে একদিকের রিকশাগুলোকে পিছিয়ে (rollback) গলির মুখে ফেরানো। কাকে পেছাতে বলবেন? যার পেছনে কম রিকশা, যে কম ঢুকেছে — অর্থাৎ যাকে পেছাতে সবচেয়ে কম খরচ। আর একই রিকশাওয়ালাকে প্রতিদিন পেছাতে বললে সে কখনো গলি পার হবে না — তাই “আজ কতবার পিছিয়েছেন” তাও হিসাবে রাখতে হয়।

৮ · Banker's বাস্তবে কেন কম, আর এর ছায়া কোথায়

Banker's সুন্দর, প্রমাণযোগ্য — তবু সাধারণ OS এটি চালায় না। কারণগুলো পরীক্ষায় “limitations” প্রশ্নের উত্তরও:

  • Max আগে জানা যায় না। একটি ব্রাউজার বা ওয়েব সার্ভার কতগুলো lock, ফাইল বা কত মেমরি চাইবে, তা আগে থেকে বলা প্রায় অসম্ভব। বেশি করে ঘোষণা করলে Banker's অকারণে অপেক্ষা করায়।
  • প্রসেসের সংখ্যা স্থির নয়। প্রসেস প্রতি মুহূর্তে তৈরি হয় আর শেষ হয়; algorithm ধরে নেয় একটি নির্দিষ্ট দল।
  • Resource-এর সংখ্যাও স্থির নয়। একটি ডিস্ক নষ্ট হতে পারে, USB খোলা হতে পারে।
  • “সীমিত সময়ে ফেরত দেবে” — নিশ্চয়তা নেই। একটি সার্ভিস দিনের পর দিন চলে।
  • খরচ। হাজারো প্রসেস আর অনেক ধরনের resource-এ প্রতিটি অনুরোধে O(m·n²) চালানো চলে না।

তাহলে চিন্তাটি কোথায় বেঁচে আছে? যেখানেই আগে ঘোষণা করা চাহিদা দিয়ে “নিতে পারব কি না” সিদ্ধান্ত হয়:

  • Admission control: real-time সিস্টেমে নতুন কাজ নেওয়ার আগে schedulability পরীক্ষা (পর্ব ২০), নেটওয়ার্কে নতুন সংযোগের আগে bandwidth আছে কি না দেখা — “ঘোষিত চাহিদা মেটানো যাবে কি না” প্রশ্ন।
  • Kubernetes-এর resource request (উপমা হিসেবে): প্রতিটি pod আগে থেকে জানায় তার কত CPU আর মেমরি লাগবে, আর scheduler তাকে এমন node-এ বসায় যেখানে বাকি অনুরোধ-না-করা ক্ষমতা যথেষ্ট; namespace-এর ResourceQuota সীমা ছাড়ালে নতুন pod গ্রহণই করা হয় না। এটি Banker's-এর “Max ঘোষণা আর Available-এর সাথে তুলনা”-র মতো দেখতে, কিন্তু মনে রাখুন — এটি deadlock-এর safety পরীক্ষা নয়, শুধু ধারণক্ষমতার হিসাব; কোনো safe sequence খোঁজা হয় না।
  • Database: avoidance নয়, detection — প্রায় প্রতিটি relational database-এ wait-for graph-ভিত্তিক deadlock detector আর victim বাতিল করার ব্যবস্থা আছে (অংশ ৬–৭)।
  • Embedded আর নিরাপত্তা-সংবেদনশীল সিস্টেম: যেখানে কাজের তালিকা আগে থেকে স্থির, সেখানে নকশার সময়ই resource-এর চাহিদা বিশ্লেষণ করে deadlock-মুক্ততা যাচাই করা হয় — Banker's-এর চিন্তা, কিন্তু চলার সময় নয়, আগেই।

আরও গভীরে: Linux kernel নিজের lock-গুলোর জন্য lockdep নামের একটি debug-ব্যবস্থা রাখে (CONFIG_PROVE_LOCKING). এটি চলার সময় প্রতিটি lock-class কোন ক্রমে নেওয়া হলো তার একটি graph গড়ে, আর কোনো সম্ভাব্য cycle দেখামাত্র সতর্ক করে — deadlock আসলে ঘটার আগেই, শুধু একবার ভুল ক্রম দেখলেই। অর্থাৎ এটি detection-এর graph চিন্তাকে ব্যবহার করে prevention-এর নিয়ম (lock ordering, পর্ব ২৭) যাচাই করে। Windows-এর Driver Verifier-এও driver-এর lock-ক্রমের এমন একটি পরীক্ষা আছে। Distributed database-এ wait-for graph একাধিক মেশিনে ছড়িয়ে থাকে, তাই সেখানে detection নিজেই একটি কঠিন গবেষণার বিষয় — ভুল করে “phantom” deadlock ধরার সমস্যাসহ।

৯ · AI-এর যুগে Banker's algorithm

Banker's-এর অঙ্ক AI-কে দিলে উত্তর দ্রুত আসে, কিন্তু এই বিষয়ে কয়েকটি ভুল বারবার দেখা যায়:

AI-এর ভুলকেন ভুলকীভাবে যাচাই করবেন
প্রসেস শেষ হলে Work-এ Need বা Max যোগ করাফেরত আসে Allocationশেষে Work = মোট resource কি না মেলান
“Unsafe মানে deadlock”Unsafe মানে শুধু নিশ্চয়তা নেই১২ tape drive-এর উদাহরণটি AI-কে দিয়ে ব্যাখ্যা চান
একটি safe sequence-কে “একমাত্র সঠিক” বলা, বা আপনার ভিন্ন সঠিক ক্রমকে ভুল বলাঅনেক safe sequence থাকতে পারেপ্রতিটি ধাপে Need ≤ Work হাতে মিলিয়ে দেখুন
পরপর অনুরোধগুলোকে শুরুর অবস্থা থেকে বিচার করা (বা উল্টোটা)রায় নির্ভর করে কোন অবস্থায় এলপ্রশ্নে কী বলা আছে লিখে দিন
Detection-এ শূন্য-Allocation নিয়ম ভুলে যাওয়াকিছু না ধরা প্রসেস deadlock-এর অংশ হতে পারে নাএমন একটি প্রসেস যোগ করে ফল দেখুন
“Linux Banker's algorithm দিয়ে deadlock এড়ায়”সাধারণ OS এটি চালায় নাKernel ডকুমেন্টেশনে lockdep পড়ুন

অঙ্ক দেওয়ার সময় নিয়মগুলো স্পষ্ট লিখে দিন, আর প্রতিটি ধাপ যাচাইযোগ্য আকারে চান:

নিচের Banker's algorithm সমস্যাটি সমাধান করো। resource A = 10, B = 5, C = 7. Allocation: P0 (0,1,0), P1 (2,0,0), P2 (3,0,2), P3 (2,1,1), P4 (0,0,2). Max: P0 (7,5,3), P1 (3,2,2), P2 (9,0,2), P3 (2,2,2), P4 (4,3,3). ১. Available আর Need টেবিল দেখাও। ২. Safety algorithm চালাও; একাধিক প্রসেস যোগ্য হলে সবচেয়ে ছোট index আগে নাও। প্রতিটি ধাপে Work আর Finish দেখাও। ৩. এরপর অনুরোধগুলো পরপর বিচার করো (প্রতিটি আগের সিদ্ধান্তের পরের অবস্থায়): P1 (1,0,2), P4 (3,3,0), P0 (0,2,0). প্রতিটির জন্য Request ≤ Need, Request ≤ Available আর safety-র ফল আলাদা করে লেখো। ৪. শেষে Work মোট resource-এর সমান হলো কি না যাচাই করো। কোথাও অনুমান করলে স্পষ্ট বলো।

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

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

  • Safe state কী? Safe state কি deadlock-মুক্ত, আর unsafe কি deadlock? — যে অবস্থায় অন্তত একটি safe sequence আছে; safe হলে deadlock অসম্ভব, unsafe হলে সম্ভব কিন্তু নিশ্চিত নয়।
  • Need কীভাবে বের করবেন? — Need = Max − Allocation, প্রতিটি প্রসেস আর resource ধরে।
  • Safety algorithm-এর time complexity? — O(m·n²), n = প্রসেস, m = resource type.
  • Avoidance আর detection-এর মূল পার্থক্য? — Avoidance আগাম Max জেনে প্রতিটি অনুরোধে unsafe অবস্থা এড়ায়; detection কিছু আটকায় না, এখনকার Request দেখে deadlock খোঁজে আর recovery করে।
  • Banker's কেন বাস্তবে কম ব্যবহৃত? — Max আগে জানা যায় না, প্রসেস আর resource-এর সংখ্যা বদলায়, আর প্রতি অনুরোধে হিসাবের খরচ বেশি।

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

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

প্র ০১Dijkstra কেন “ব্যাংকার”-এর গল্প বেছে নিয়েছিলেন? ১৯৬০-এর দশকের THE সিস্টেমের প্রেক্ষাপটে avoidance-এর চিন্তা কতটা স্বাভাবিক ছিল?

১৯৬০-এর দশকের মাঝামাঝি multiprogramming নতুন বাস্তবতা — একই কম্পিউটারে কয়েকটি প্রোগ্রাম পালা করে চলে, আর মেমরি, ড্রাম, টেপ, প্রিন্টারের মতো দামি resource তারা ভাগ করে। Eindhoven-এ Dijkstra ও তাঁর ছোট দল THE multiprogramming সিস্টেম লিখছিলেন, যা স্তরে স্তরে সাজানো নকশা আর semaphore-এর ব্যবহারের জন্য বিখ্যাত (পর্ব ২৪)। এমন সিস্টেমে প্রশ্ন ওঠে: দুটি প্রোগ্রাম যদি একে অপরের ধরে রাখা resource-এর জন্য অপেক্ষা করে, কে ছাড়বে? সে যুগে একটি deadlock মানে অপারেটরকে হাতে মেশিন থামাতে হতো, আর ঘণ্টার পর ঘণ্টার কাজ নষ্ট।

ব্যাংকারের উপমাটি সমস্যাটিকে অসাধারণভাবে পরিষ্কার করে। ব্যাংকার সব গ্রাহককে একসাথে পুরো সীমা দিতে পারেন না — তহবিল সীমিত। কিন্তু তিনি জানেন, প্রত্যেক গ্রাহক নিজের সীমা পর্যন্ত পেলে কাজ শেষ করে টাকা ফেরত দেবেন। তাই প্রশ্নটি “এখন কতটা খালি আছে” নয়, বরং “এখন দিলে কি এমন একটি ক্রম থাকবে, যাতে একে একে সবাইকে পুরো সীমা দেওয়া যায়?” এই প্রশ্নই safe state. উপমাটির শক্তি হলো, এটি দেখায় কেন খালি থাকা সত্ত্বেও “না” বলা যুক্তিসঙ্গত — যা প্রোগ্রামারদের কাছে প্রথমে অদ্ভুত লাগত।

একই সাথে উপমাটি algorithm-এর সীমাও লুকিয়ে রাখে না। ব্যাংকের গ্রাহক চুক্তি করে সীমা ঠিক করেন; একটি প্রোগ্রাম কি আগে বলতে পারে সে কতগুলো টেপ চাইবে? THE-এর মতো সাবধানে নকশা করা, স্থির কাজের সিস্টেমে হয়তো পারে; সাধারণ উদ্দেশ্যের সিস্টেমে কঠিন। মূল রূপটি ছিল একটিমাত্র ধরনের resource-এর জন্য; একাধিক ধরনে সাধারণীকরণ (পরে Habermann-সহ অন্যদের কাজে) আজকের পাঠ্যবইয়ের রূপ। আর পরের দশকগুলোতে বাস্তব OS বেশিরভাগ ক্ষেত্রে prevention (lock ordering) আর “উপেক্ষা” বেছে নিয়েছে, কিন্তু safe state-এর ধারণা তাত্ত্বিক ভিত্তি হিসেবে টিকে আছে — একটি স্পষ্ট মাপকাঠি, যার সাথে অন্য সব কৌশল তুলনা করা যায়।

মূল উপলব্ধি: একটি ভালো উপমা শুধু ব্যাখ্যা করে না, নকশার অনুমানগুলোও (আগাম সীমা, নিশ্চিত ফেরত) সামনে আনে — আর সেই অনুমানগুলোই ঠিক করে algorithm বাস্তবে কোথায় চলবে।

প্র ০২Banker's algorithm unsafe অবস্থাকে deadlock-এর মতোই এড়িয়ে চলে। এই অতি-সতর্কতা কতটা দামি? কোন পরিস্থিতিতে এই দাম দেওয়া যুক্তিসঙ্গত?

এই পর্বের উদাহরণে দাম চোখে পড়ে: P0 দুটি B চেয়েছিল, দুটিই খালি ছিল, তবু সে অপেক্ষায়। Resource অলস পড়ে থাকল, P0 থেমে থাকল। ১২ tape drive-এর উদাহরণেও P2-কে একটি drive না দেওয়ায় অবস্থা safe থাকল, কিন্তু যদি বাস্তবে P0 কখনো আর drive না চাইত, তাহলে সেই অপেক্ষা পুরোপুরি অকারণ। Avoidance সব সময় “সবচেয়ে খারাপ ভবিষ্যৎ” ধরে নেয় — প্রত্যেকে নিজের ঘোষিত সর্বোচ্চ চাইবে। ঘোষিত Max বাস্তব চাহিদার চেয়ে যত বড়, অপচয় তত বেশি। প্রোগ্রামাররা নিরাপদ থাকতে সাধারণত Max বাড়িয়েই বলেন, তাই বাস্তবে অপচয় আরও বাড়ে।

এর সাথে তুলনা করুন detection-এর পথ: সব অনুরোধ খালি থাকলেই মেটানো, আর কালেভদ্রে deadlock হলে কাউকে বাতিল করা। যদি deadlock খুবই বিরল হয়, আর বাতিল করা সস্তা (যেমন একটি database transaction, যা অ্যাপ্লিকেশন আবার চালাতে পারে), তাহলে detection মোট throughput-এ অনেক ভালো। এ কারণেই database avoidance নয়, detection বেছে নিয়েছে। অন্যদিকে যদি rollback অসম্ভব বা বিপজ্জনক হয় — যেমন একটি রোবট-বাহু অর্ধেক ঘুরে থেমে যাওয়া, একটি কারখানার মেশিনের নিয়ন্ত্রণ, একটি চিকিৎসা-যন্ত্র — তাহলে deadlock হওয়ার পর উদ্ধার গ্রহণযোগ্য নয়। সেখানে কাজের তালিকা সাধারণত স্থির, চাহিদা আগে জানা, আর কিছু resource অলস রাখার দাম নিশ্চয়তার তুলনায় সামান্য।

অর্থনীতির ভাষায় এটি বিমার মতো: avoidance নিয়মিত প্রিমিয়াম (অলস resource, অপেক্ষা) দিয়ে বিরল দুর্যোগ এড়ায়; detection প্রিমিয়াম দেয় না, দুর্যোগ এলে ক্ষতি মেনে নেয়। কোনটি ভালো তা নির্ভর করে দুর্যোগের সম্ভাবনা আর ক্ষতির পরিমাণের ওপর। Prevention (lock ordering) তৃতীয় পথ — নকশার সময় একবার খরচ, চলার সময় প্রায় বিনামূল্যে — তাই সাধারণ সফটওয়্যারে সেটিই সবচেয়ে জনপ্রিয়।

মূল উপলব্ধি: নিশ্চয়তার দাম আছে; যেখানে deadlock-এর ক্ষতি বিপর্যয়কর আর চাহিদা আগে জানা, সেখানে avoidance-এর অপচয় যুক্তিসঙ্গত — বাকি জায়গায় detection বা prevention সস্তা।

প্র ০৩Database-এ deadlock হলে একটি transaction বাতিল করা হয়। OS-এ একটি সাধারণ প্রসেসকে একইভাবে “rollback” করা এত কঠিন কেন?

Database-এর transaction জন্ম থেকেই rollback-এর জন্য তৈরি। প্রতিটি বদল আগে log-এ লেখা হয় (write-ahead logging), আর lock-গুলো database নিজেই দেয় আর রাখে। ফলে deadlock ধরা পড়লে একটি transaction-কে বাতিল করার মানে — তার সব বদল log দেখে উল্টে দেওয়া আর তার lock ছেড়ে দেওয়া; বাকি পৃথিবী জানতেই পারে না সে কিছু করেছিল। Atomicity-র নিশ্চয়তা (DBMS কোর্সের ACID পর্ব দেখুন) এই কাজটিকে নিরাপদ করে। অ্যাপ্লিকেশনকে শুধু একটি নির্দিষ্ট error পেয়ে transaction আবার চালাতে হয়।

একটি সাধারণ OS প্রসেসের ক্ষেত্রে ছবিটি একেবারে আলাদা। প্রসেস ফাইলে লিখেছে, নেটওয়ার্কে বার্তা পাঠিয়েছে, অন্য প্রসেসের সাথে pipe বা shared memory-তে কথা বলেছে, হয়তো স্ক্রিনে কিছু দেখিয়েছে বা প্রিন্টারে পাতা ছেপেছে। এগুলোর কোনো সাধারণ “undo” নেই — পাঠানো বার্তা ফেরানো যায় না, ছাপা কাগজ সাদা হয় না। OS প্রসেসের মেমরি ছবি (checkpoint) রাখতে পারে, আর কিছু গবেষণা বা বিশেষ সিস্টেম (যেমন Linux-এ CRIU-র মতো checkpoint/restore টুল, বা high-performance computing-এ দীর্ঘ হিসাবের নিয়মিত checkpoint) সত্যিই তা করে। কিন্তু বাইরের পৃথিবীতে যা ঘটে গেছে, তা ফেরানো যায় না; আর অন্য প্রসেস সেই ফল দেখে নিজেও এগিয়ে গেছে — তাদেরও ফেরাতে হলে একটি “domino effect” শুরু হয়।

এ কারণে OS-এ recovery সাধারণত মানে প্রসেস বন্ধ করা (kill), rollback নয় — আর তার পরিণতি (অর্ধেক লেখা ফাইল) সামলানোর দায় প্রোগ্রামের। ভালো প্রোগ্রাম তাই নিজের ভেতরে transaction-এর মতো কৌশল ব্যবহার করে: নতুন ফাইলে লিখে শেষে rename করা, journaling file system-এর ওপর ভরসা করা (পর্ব ৪৪)।

মূল উপলব্ধি: Rollback তখনই সস্তা, যখন সিস্টেম শুরু থেকেই প্রতিটি বদলের হিসাব রাখে আর বাইরের প্রভাব নিয়ন্ত্রণ করে — database তা করে, সাধারণ প্রসেস করে না।

প্র ০৪Detection algorithm কত ঘন ঘন চালাবেন — প্রতিটি অপেক্ষায়, নাকি মাঝে মাঝে? PostgreSQL আর InnoDB-র ভিন্ন পছন্দ থেকে কী শেখা যায়?

এটি একটি পরিচিত প্রকৌশল দর-কষাকষি: দ্রুত ধরা বনাম কম খরচ। প্রতিটি অপেক্ষায় পরীক্ষা চালালে deadlock সঙ্গে সঙ্গে ধরা পড়ে, আর যে অনুরোধটি cycle বন্ধ করল তাকেই “দোষী” হিসেবে বাতিল করা সহজ — আটকে থাকা প্রসেস বা transaction-এর সংখ্যা ছোট থাকে। কিন্তু বেশিরভাগ অপেক্ষা deadlock নয়: একটি row-lock সাধারণত কয়েক মিলিসেকেন্ডে ছেড়ে দেওয়া হয়। হাজার হাজার একসাথে চলা transaction-এ প্রতিটি অপেক্ষায় graph খোঁজা নিজেই একটি বোঝা হয়ে উঠতে পারে, আর graph-এর ভাগ করা ডেটা স্ট্রাকচার রক্ষার lock নিজেই প্রতিযোগিতার জায়গা হয়ে ওঠে।

PostgreSQL তাই একটি “আশাবাদী” পথ নেয়: একটি lock-এর জন্য অপেক্ষা deadlock_timeout (ডিফল্ট ১ সেকেন্ড) পেরোলেই কেবল পরীক্ষা চালায়। যুক্তি — এর মধ্যে বেশিরভাগ অপেক্ষা নিজেই মিটে যাবে, আর সত্যিকারের deadlock এক সেকেন্ড দেরিতে ধরা পড়লে সাধারণত বড় ক্ষতি নেই। MySQL-এর InnoDB ডিফল্টভাবে অপেক্ষা শুরু হতেই পরীক্ষা করে, যাতে deadlock-এ পড়া transaction দ্রুত বাতিল হয়; কিন্তু খুব বেশি concurrency-র কাজে এই পরীক্ষাই ধীরগতির কারণ হলে প্রশাসক innodb_deadlock_detect বন্ধ করে শুধু lock-wait timeout-এর ওপর নির্ভর করতে পারেন। দুই নকশাই যুক্তিসঙ্গত; ভিন্ন workload-এ ভিন্নটি ভালো।

OS-এর পাঠ্যবইয়ের পরামর্শও একই কাঠামোর: deadlock ঘন ঘন হলে ঘন ঘন চালান, কারণ দেরিতে ধরলে আটকে থাকা দল বড় হয়, আর বেশি প্রসেসের resource অলস থাকে; বিরল হলে মাঝে মাঝে, বা CPU-র ব্যবহার হঠাৎ কমে গেলে চালান। একটি সূক্ষ্ম দিক: মাঝে মাঝে চালালে graph-এ কয়েকটি cycle একসাথে থাকতে পারে, আর কোন অনুরোধ “দোষী” তা বলা যায় না — victim বাছাই তখন শুধু খরচের হিসাব।

মূল উপলব্ধি: Detection-এর ঘনত্ব একটি tuning-এর প্রশ্ন — deadlock কত সাধারণ আর পরীক্ষা কত দামি, এই দুই মেপে ঠিক করতে হয়; একটি ডিফল্ট সব workload-এ ঠিক নয়।

প্র ০৫Kubernetes-এর resource request আর quota কি Banker's algorithm? মিল কোথায়, আর কোথায় এই উপমা ভেঙে পড়ে?

মিলটি আকর্ষণীয়। Kubernetes-এ প্রতিটি container আগে থেকে ঘোষণা করে তার কত CPU আর মেমরি “request” লাগবে, চাইলে একটি ঊর্ধ্বসীমা “limit”-ও। Scheduler একটি নতুন pod-কে কেবল এমন node-এ বসায়, যেখানে আগে থেকে বসানো pod-গুলোর request যোগ করেও নতুনটির জায়গা থাকে; আর namespace-এর ResourceQuota ছাড়িয়ে গেলে API server pod-টি গ্রহণই করে না — এটি admission control. Banker's-এর ভাষায়: ঘোষিত চাহিদা (Max-এর মতো) আর খালি ক্ষমতা (Available-এর মতো) তুলনা করে “নেব কি না” সিদ্ধান্ত। চিন্তার মূলটি এক — চলার আগেই চাহিদা জানা থাকলে অনেক সমস্যা আগে আটকানো যায়।

কিন্তু উপমাটি তিন জায়গায় ভাঙে। প্রথমত, Banker's একটি ক্রমের অস্তিত্ব খোঁজে — কে আগে শেষ করে resource ফেরত দেবে, যাতে পরের জন চলতে পারে। Kubernetes এমন কিছু খোঁজে না; সে শুধু দেখে এই মুহূর্তে জায়গা আছে কি না। দ্বিতীয়ত, Banker's ধরে নেয় প্রসেস সর্বোচ্চ পেলে সীমিত সময়ে শেষ করে সব ফেরত দেবে; Kubernetes-এর বেশিরভাগ pod (ওয়েব সার্ভিস) কখনো শেষ হয় না। তৃতীয়ত, deadlock-এর প্রশ্নটিই আলাদা: CPU আর মেমরি এখানে প্রায় সবসময় একটি pod-এর ভেতরে স্থানীয়ভাবে বরাদ্দ হয়, দুটি pod একে অপরের CPU ধরে বসে থাকে না। ক্লাস্টারে যে “আটকে যাওয়া” দেখা যায় — যেমন কয়েকটি pod-এর একসাথে চলা দরকার, কিন্তু অর্ধেক বসে বাকিরা জায়গা পায় না (gang scheduling-এর সমস্যা) — তা সামলাতে আলাদা scheduler বা plugin লাগে।

আরেকটি পার্থক্য দর্শনের: Banker's সতর্ক, Kubernetes প্রায়ই ইচ্ছা করে “overcommit” করে — limit-এর যোগফল node-এর ক্ষমতার বেশি হতে পারে, কারণ সবাই একসাথে সর্বোচ্চ চায় না। চাপ এলে QoS শ্রেণি অনুযায়ী কাউকে বের করে দেওয়া (eviction) হয় — এটি বরং detection-আর-recovery-র কাছাকাছি।

মূল উপলব্ধি: “আগে ঘোষণা, তারপর গ্রহণ” — Banker's-এর এই অর্ধেকটি ক্লাউডে খুব জীবন্ত; কিন্তু safe sequence-এর অর্ধেকটি সেখানে নেই, তাই একে Banker's বলা উপমা, সমতা নয়।

অনুশীলন

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

  1. হাতে হিসাব: চারটি প্রসেস P0–P3, তিন ধরনের resource. Allocation: P0 (১, ১, ০), P1 (২, ০, ১), P2 (১, ১, ১), P3 (০, ১, ১)। Max: P0 (৪, ২, ২), P1 (৩, ৩, ২), P2 (২, ২, ২), P3 (৫, ২, ৩)। Available = (২, ১, ১)। (ক) Need টেবিল লিখুন। (খ) সিস্টেম কি safe? safe sequence দিন (ছোট index আগে)। (গ) এই অবস্থায় P0 (১, ১, ০) চাইলে কি মঞ্জুর হবে?

    (ক) Need = Max − Allocation: P0 (৩, ১, ২), P1 (১, ৩, ১), P2 (১, ১, ১), P3 (৫, ১, ২)।

    (খ) Work (২, ১, ১): P0-এর A ৩ > ২, P1-এর B ৩ > ১ — বাদ; P2 (১, ১, ১) ≤ (২, ১, ১) → Work (৩, ২, ২)। আবার শুরু থেকে: P0 (৩, ১, ২) ≤ (৩, ২, ২) → (৪, ৩, ২)। P1 (১, ৩, ১) ≤ (৪, ৩, ২) → (৬, ৩, ৩)। P3 (৫, ১, ২) ≤ (৬, ৩, ৩) → (৬, ৪, ৪)। Safe, sequence <P2, P0, P1, P3>; শেষ Work (৬, ৪, ৪) = মোট resource.

    (গ) (১, ১, ০) ≤ Need (৩, ১, ২) আর ≤ Available (২, ১, ১) — দুই পরীক্ষা পাস। ধরে নিলে Available (১, ০, ১), P0-এর Need (২, ০, ২)। এখন P0-এর A ২ > ১, P1-এর B ৩ > ০, P2-এর B ১ > ০, P3-এর A ৫ > ১ — কেউ শুরু করতে পারে না। Unsafe, প্রত্যাখ্যাত; P0 অপেক্ষা করবে। (Banker's simulator-এ max, alloc, avail বদলে মিলিয়ে নিতে পারেন।)

  2. আউটপুট অনুমান: Detection simulator-এর অবস্থা ২-এ একটি ষষ্ঠ প্রসেস P5 যোগ করা হলো, যে কিছুই ধরে নেই (Allocation ০, ০, ০) কিন্তু একটি A-এর জন্য অপেক্ষা করছে (Request ১, ০, ০)। আউটপুটের শেষ লাইন কী হবে? আর যদি কোডে শূন্য-Allocation নিয়মটি না থাকত (সবার Finish শুরুতে false)?

    নিয়মসহ: P5-এর Finish শুরু থেকেই true, তাই শেষ লাইন অপরিবর্তিত — DEADLOCKED: P1, P2, P3, P4. নিয়ম ছাড়া: P0-এর পর Work (০, ১, ০), আর P5-এর A ১ > ০, তাই সেও আটকে থাকে, আর লাইনটি হয় DEADLOCKED: P1, P2, P3, P4, P5 — যা ভুল। P5 কিছুই ধরে নেই, তাই কেউ তার জন্য অপেক্ষা করে না; সে কোনো cycle-এর অংশ হতে পারে না। সে শুধু আটকে থাকা দলের কারণে অপেক্ষা করছে (wait-for graph-এর P4-এর মতো)। Deadlock ভাঙলে সে নিজেই এগোবে; তাকে বন্ধ করা অকারণ ক্ষতি। (Simulator-এ দুটি সংস্করণ চালিয়ে আমরা ঠিক এই দুই ফল পেয়েছি।)

  3. কোড বদলান: Banker's simulator-এ একটি চতুর্থ অনুরোধ যোগ করুন — request(3, [1,1,1]);. চালানোর আগে বলুন কী ছাপা হবে, আর কেন।

    ছাপা হবে P3 requests (1,1,1): ERROR, more than its Need. P3-এর Max (২, ২, ২), Allocation (২, ১, ১), তাই Need (০, ১, ১)। চাওয়া (১, ১, ১)-এর A = ১ > ০ — প্রসেস নিজের ঘোষিত সর্বোচ্চের বেশি চাইছে। Resource-request algorithm-এর প্রথম পরীক্ষাতেই এটি আটকায়; Available বা safety দেখার প্রশ্নই ওঠে না। বাস্তব সিস্টেমে এটি “প্রোগ্রামের বাগ” — ঘোষণা ভুল ছিল, তাই সাধারণত প্রসেসটিকে error দেওয়া বা বন্ধ করা হয়। মনে রাখুন, আগের তিনটি অনুরোধে অবস্থা বদলায়নি এমন নয় — P1-এর অনুরোধ মঞ্জুর হয়েছিল — কিন্তু P3-এর টেবিলে তার প্রভাব নেই।

  4. ব্যাখ্যা করুন: একটি ধরনের resource, মোট ১২টি। P0 (Max ১০, ধরে ৫), P1 (Max ৪, ধরে ২), P2 (Max ৯, ধরে ২)। (ক) সিস্টেম কি safe? (খ) P2 একটি চাইলে Banker's কী করবে? (গ) ধরুন OS না ভেবে দিয়েই দিল — একটি ঘটনাক্রম দিন যাতে deadlock হয়, আর একটি যাতে হয় না।

    (ক) খালি ১২ − ৯ = ৩. Need: P0 ৫, P1 ২, P2 ৭। <P1, P0, P2>: ৩ → ৫ → ১০ → ১২. Safe.

    (খ) ধরে নিলে খালি ২, P2-এর Need ৬. P1 শেষ করে খালি হয় ৪; তারপর P0-এর ৫ আর P2-এর ৬ — কেউ হয় না। Unsafe, তাই প্রত্যাখ্যাত, P2 অপেক্ষা করবে, যদিও ২টি drive খালি।

    (গ) Deadlock-এর ক্রম: P1 নিজের বাকি ২টি নিয়ে শেষ করে ফেরত দেয় (খালি ৪)। তারপর P0 চায় ৪টি — পায় (খালি ০), আর আরও ১টির জন্য অপেক্ষা করে; P2 চায় আরও — খালি নেই। P0 আর P2 দুজনেই অপেক্ষায়, কেউ ছাড়ে না: deadlock. Deadlock-হীন ক্রম: P1 শেষ করে (খালি ৪), তারপর P0 আর কিছু না চেয়ে নিজের ৫টি দিয়েই কাজ শেষ করে ফেরত দেয় (খালি ৯) — এবার P2-এর ৬ মিটে যায়। একই unsafe অবস্থা থেকে দুই ভিন্ন ভবিষ্যৎ — এটাই “unsafe ≠ deadlock”.

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

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

ক্রমBanker's algorithm-এ একটি প্রসেসের অনুরোধ এলে OS-এর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Request ≤ Need কি না দেখা — না হলে error
  2. Request ≤ Available কি না দেখা — না হলে অপেক্ষা
  3. দিয়েছি ধরে নিয়ে Available, Allocation আর Need বদলানো
  4. নতুন অবস্থায় safety algorithm চালানো
  5. Safe হলে মঞ্জুর, unsafe হলে বদল ফিরিয়ে প্রসেসকে অপেক্ষা করানো

Safe state আর deadlock-এর সম্পর্ক কোনটি সঠিক?

  • প্রতিটি unsafe অবস্থাই deadlock
  • Safe অবস্থাতেও deadlock হতে পারে, যদি প্রসেস বেশি চায়
  • Safe অবস্থায় deadlock অসম্ভব; deadlock অবস্থা unsafe-এর অংশ, কিন্তু প্রতিটি unsafe অবস্থা deadlock নয়
  • Safe আর deadlock-মুক্ত অবস্থা আলাদা কিছু নয়, আর unsafe মানে deadlock

একটি প্রসেসের Max = (৭, ৫, ৩) আর Allocation = (০, ১, ০)। তার Need কত?

  • (৭, ৬, ৩)
  • (৭, ৪, ৩)
  • (৭, ৫, ৩)
  • (−৭, −৪, −৩)

n টি প্রসেস আর m ধরনের resource-এ Banker's-এর safety algorithm-এর worst-case time complexity কত?

  • O(n)
  • O(n²)
  • O(m·n)
  • O(m·n²)

Available = (২, ৩, ০)। একটি প্রসেসের Need = (৪, ৩, ১), আর সে Request = (৩, ৩, ০) পাঠাল। Banker's algorithm কী করবে?

  • প্রসেসকে অপেক্ষা করাবে, কারণ Request ≤ Available নয়
  • Error দেবে, কারণ Request Need-এর বেশি
  • সঙ্গে সঙ্গে মঞ্জুর করবে
  • আগে safety algorithm চালাবে, তারপর ঠিক করবে

একাধিক instance-এর deadlock detection algorithm-এ যে প্রসেসের Allocation শূন্য, তার Finish শুরুতেই true ধরা হয় কেন?

  • কারণ সে ইতিমধ্যে শেষ হয়ে গেছে
  • কারণ তার Request-ও নিশ্চয়ই শূন্য
  • কারণ কিছু না ধরে থাকা প্রসেস কোনো cycle-এর অংশ হতে পারে না, তাই সে deadlocked নয়
  • শুধু algorithm দ্রুত করার জন্য, ফলের ওপর এর প্রভাব নেই

Deadlock recovery-তে resource preemption ব্যবহার করলে একই প্রসেস বারবার victim হয়ে starvation-এ পড়তে পারে। প্রচলিত সমাধান কোনটি?

  • প্রতিবার deadlock-এ সব প্রসেস বন্ধ করা
  • Victim বাছাইয়ের খরচের হিসাবে প্রসেসটি এ পর্যন্ত কতবার rollback হয়েছে, তা যোগ করা
  • Detection algorithm আরও ঘন ঘন চালানো
  • সবসময় সবচেয়ে কম priority-র প্রসেসকে victim করা

শব্দকোষ

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

মনে রাখুন

  1. Safe state: অন্তত একটি safe sequence আছে। Safe ⇒ deadlock অসম্ভব; unsafe মানে শুধু নিশ্চয়তা নেই — deadlock সম্ভব, নিশ্চিত নয়।
  2. এক instance: claim edge-সহ graph-এ অনুরোধকে assignment edge বানালে cycle হলে প্রত্যাখ্যান। Detection-এ wait-for graph-এ cycle = deadlock.
  3. Banker's: Need = Max − Allocation; safety-তে Need ≤ Work হলে Work += Allocation; শেষে Work = মোট resource. খরচ O(m·n²). পাঠ্যবইয়ের উদাহরণ: safe, <P1, P3, P0, P2, P4> (ছোট index আগে)।
  4. অনুরোধ: ≤ Need (না হলে error) → ≤ Available (না হলে অপেক্ষা) → ধরে নেওয়া → safety → মঞ্জুর বা roll back. P1 (১, ০, ২) মঞ্জুর; তারপর P4 (৩, ৩, ০) অপেক্ষা; P0 (০, ২, ০) unsafe বলে প্রত্যাখ্যাত।
  5. Detection ও recovery: Request দিয়ে একই ধাঁচের algorithm, শূন্য-Allocation হলে শুরুতেই Finish = true; recovery মানে বন্ধ করা (সবাই বা একজন একজন) বা preemption + rollback, খরচে rollback-সংখ্যা যোগ করে starvation ঠেকানো। বাস্তবে Banker's কম, detection database-এ সাধারণ।

পরের পর্বে: Deadlock-ই কনকারেন্সির একমাত্র বিপদ নয়। পর্ব ২৯-এ কনকারেন্সির অন্য বাগ — priority inversion — আর lock-free ও RCU-র মতো আধুনিক কৌশল।

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

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