ক্লাসিক সিনক্রোনাইজেশন: ডাইনিং ফিলোসফার্স
এই পাঠে যা শিখবেন
- ডাইনিং ফিলোসফার্স সমস্যার সেটআপ এবং এটি কেন সাধারণ রিসোর্স-ডেডলক সিনারিওর একটি মডেল
- নাইভ "বাম ফর্ক আগে" সমাধান ঠিক কীভাবে, কোন নির্দিষ্ট শর্তে, সম্পূর্ণ ডেডলকে পরিণত হয়
- N-1 ফিলোসফার ফিক্স কীভাবে ডেডলক পুরোপুরি প্রতিরোধ করে — এবং কেন এটি pigeonhole নীতির একটি প্রয়োগ
- কোডে উভয় সিনারিও (ডেডলক ও তার সমাধান) সরাসরি ধাপে ধাপে ট্রেস করে দেখা
১ · সমস্যাটির সেটআপ
ডাইনিং ফিলোসফার্স সমস্যাDining Philosophers ProblemDijkstra-র ক্লাসিক দৃষ্টান্ত -- N জন দার্শনিক গোল টেবিলে, N-টি ফর্ক, প্রত্যেকের খেতে দুটো ফর্কই দরকার -- সাধারণ রিসোর্স-ডেডলক মডেল করে। -তে ৫ জন দার্শনিক একটি গোল টেবিলে বসে আছেন, আর তাদের মাঝে ঠিক ৫টি ফর্ক (প্রতি দুইজনের মাঝে একটি করে)। প্রতিটি দার্শনিকের খেতে হলে তার বাম ও ডান — দুটো ফর্কই একসাথে দরকার। এটি প্রকৃতপক্ষে খাওয়াদাওয়া নিয়ে নয় — এটি Dijkstra-র একটি ক্লাসিক দৃষ্টান্ত, যা সাধারণভাবে একাধিক প্রসেসের একাধিক শেয়ার্ড রিসোর্সের জন্য প্রতিযোগিতাকে মডেল করে (দার্শনিক = প্রসেস, ফর্ক = রিসোর্স)।
২ · নাইভ সমাধান ও তার ডেডলক
সবচেয়ে সহজ (কিন্তু ত্রুটিপূর্ণ) সমাধান — প্রতিটি দার্শনিক সবসময় প্রথমে তার বাম ফর্ক তোলে, তারপর ডান ফর্ক। এটি একজন দার্শনিকের জন্য যুক্তিসঙ্গত মনে হলেও, যদি সব ৫ জনই একইসাথে তাদের বাম ফর্ক তুলে ফেলে, তাহলে প্রতিটি ফর্কই এখন কারো-না-কারো হাতে — কেউই তার ডান ফর্ক পাবে না, কারণ সেটা তার ডান পাশের প্রতিবেশীর "বাম ফর্ক" হিসেবে ইতিমধ্যে দখল হয়ে গেছে। ফলাফল — একটি নিখুঁত বৃত্তাকার অপেক্ষা (circular wait): P0 → F1-এর জন্য অপেক্ষা করছে (P1-এর কাছে), P1 → F2-এর জন্য (P2-এর কাছে), ... P4 → F0-এর জন্য (P0-এর কাছে) — একটি সম্পূর্ণ চক্র, এবং M6/L23-এর ডেডলকের চারটি প্রয়োজনীয় শর্তই এখানে একসাথে পূরণ হয় (mutual exclusion — একটি ফর্ক একবারে একজনই ধরতে পারে; hold-and-wait — প্রত্যেকে এক ফর্ক ধরে রেখেই আরেকটির অপেক্ষা করছে; no preemption — কারো ফর্ক জোর করে কেড়ে নেওয়া হচ্ছে না; circular wait — উপরের চক্রটি)।
N = 5 # ৫টি ফর্ক, ৫ জন দার্শনিক
def left_fork(i):
return i
def right_fork(i):
return (i + 1) % N
print("=== নাইভ সমাধান: সবাই একসাথে বাম ফর্ক ধরে, তারপর ডান ফর্ক চেষ্টা করে ===")
forks = [True] * N # True = ফাঁকা
holding_left = [False] * N
print("-- Phase 1: সবাই নিজের বাম ফর্ক ধরলো (একসাথে, simultaneously) --")
for i in range(N):
forks[left_fork(i)] = False
holding_left[i] = True
print(f" P{i}: বাম ফর্ক F{left_fork(i)} ধরলো -> fork state: {forks}")
print("\n-- Phase 2: সবাই এখন নিজের ডান ফর্ক চেষ্টা করছে --")
stuck = []
for i in range(N):
rf = right_fork(i)
if forks[rf]:
forks[rf] = False
print(f" P{i}: ডান ফর্ক F{rf} পাওয়া গেলো")
else:
stuck.append(i)
print(f" P{i}: ডান ফর্ক F{rf} unavailable (P{rf} এর বাম হিসেবে আগেই ধরা) -> BLOCKED")
print(f"\nblocked philosophers: {stuck}")
if len(stuck) == N:
print("সবাই (5/5) আটকে গেলো -- DEADLOCK! কেউ নিজের ডান ফর্ক পাচ্ছে না, কেউ ফর্কও ছাড়ছে না।")
False (কেউ-না-কেউ ধরে ফেলেছে)। Phase 2-এ প্রতিটি
দার্শনিক তার ডান ফর্ক চায়, কিন্তু সেটি ইতিমধ্যে তার ডান প্রতিবেশীর "বাম ফর্ক" হিসেবে ধরা — তাই stuck
তালিকায় ঠিক ৫ জনই (সবাই) পড়ে যায়। কেউই আর কখনো এগোতে পারবে না, কারণ কেউই তার ধরে রাখা ফর্ক ছাড়ছে না (সে নিজেও
অপেক্ষা করছে) — এটিই ডেডলকের সংজ্ঞা।
৩ · ফিক্স — সর্বোচ্চ N-1 জন একসাথে বসতে দেওয়া
সবচেয়ে সহজ, প্রমাণযোগ্য ফিক্স — একসাথে সর্বোচ্চ N-1 জন দার্শনিককে বসতে/ফর্ক তুলতে দেওয়া (৫ জনের মধ্যে সর্বোচ্চ ৪ জন)। এটি pigeonhole নীতি-র একটি সরাসরি প্রয়োগ (Discrete Mathematics কোর্সে বিস্তারিত থাকলে সরাসরি প্রযোজ্য) — যদি ৫টি ফর্কের বিপরীতে সর্বোচ্চ ৪ জন প্রতিযোগিতা করে, অন্তত একটি ফর্ক সবসময় এমন থাকবে যা কারো "বাম ফর্ক" হিসেবে দাবি করা হয়নি — সেই একজন দার্শনিক নিশ্চিতভাবে তার দুটো ফর্কই পাবে, খেয়ে ফর্ক ছেড়ে দেবে, এবং এভাবে ধীরে ধীরে বাকি সবাই এগোতে পারবে। (আরেকটি পরিচিত ফিক্স — অ্যাসিমেট্রিক সমাধান: একজন নির্দিষ্ট দার্শনিককে আগে ডান ফর্ক তুলতে বলা, যা পুরোপুরি প্রতিসাম্য ভেঙে দেয় — আমরা এখানে N-1 পদ্ধতিটিই কোডে বাস্তবায়ন করব।)
print("=== ফিক্স: N-1 দার্শনিক বসানো (৫ জনের মধ্যে মাত্র ৪ জন একসাথে) ===")
seated = [0, 1, 2, 3] # P4 এই রাউন্ডে বসছে না
forks2 = [True] * N
holding = {i: [False, False] for i in seated} # [বাম, ডান]
eaten = set()
print(f"seated philosophers: {seated} (P4 অপেক্ষা করছে)")
print("-- Phase 1: seated সবাই নিজের বাম ফর্ক ধরলো --")
for i in seated:
forks2[left_fork(i)] = False
holding[i][0] = True
print(f" P{i}: বাম ফর্ক F{left_fork(i)} ধরলো -> fork state: {forks2}")
phase = 2
while len(eaten) < len(seated):
print(f"\n-- Phase {phase}: যারা এখনো খায়নি তারা ডান ফর্ক চেষ্টা করছে --")
progressed = False
for i in seated:
if i in eaten:
continue
rf = right_fork(i)
if not holding[i][1]:
if forks2[rf]:
forks2[rf] = False
holding[i][1] = True
progressed = True
print(f" P{i}: ডান ফর্ক F{rf} পাওয়া গেলো!")
else:
print(f" P{i}: ডান ফর্ক F{rf} এখনো unavailable -> অপেক্ষা করছে")
if holding[i][0] and holding[i][1]:
print(f" P{i}: EATING (দুটোই ফর্ক আছে) -> খাওয়া শেষ, দুটো ফর্কই ছেড়ে দিলো")
forks2[left_fork(i)] = True
forks2[rf] = True
holding[i] = [False, False]
eaten.add(i)
progressed = True
if not progressed:
print("কোনো progress হলো না -- deadlock থাকতো")
break
phase += 1
print(f"\ntotal eaten: {len(eaten)} / {len(seated)}")
if len(eaten) == len(seated):
print("সবাই (৪/৪ seated) সফলভাবে খেতে পেরেছেন -- কোনো DEADLOCK হয়নি!")
ডাইনিং ফিলোসফার্স সমস্যা দেখায় — ডেডলক ঘটার জন্য চারটি শর্তই (mutual exclusion, hold-and-wait, no preemption, circular wait) একসাথে সত্যি হতে হয়; যেকোনো একটি ভাঙলেই ডেডলক অসম্ভব হয়ে যায়। এখানে N-1 ফিক্স মূলত hold-and-wait-কে (আংশিকভাবে, নিশ্চিতভাবে কারো জন্য একটি ফর্ক সবসময় ফাঁকা রেখে) ভেঙে দেয়। এই একই চারটি শর্ত, তাদের নির্দিষ্ট সংজ্ঞা, এবং প্রতিরোধের আরও কৌশল (যেমন সব রিসোর্স একসাথে চাওয়া, বা resource ordering — ফর্কগুলো নাম্বার করে সবসময় ছোট নাম্বার আগে চাওয়া) — এই সবকিছু M6/L23-এ বিস্তারিত আলোচনা করা হবে, যেখানে এই একই দৃষ্টান্তটি আবার একটি resource-allocation গ্রাফ হিসেবে মডেল করা হবে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ যদি মাত্র ২ জন দার্শনিক একসাথে তাদের বাম ফর্ক তুলত (বাকি ৩ জন দেরিতে শুরু করত), তাহলে কি তখনও ডেডলক হতো?
না, শুধু ২ জন (ধরুন P0 ও P1) একসাথে বাম ফর্ক তুললে, P0-এর ডান ফর্ক (F1) P1-এর বাম ফর্ক হিসেবে ধরা থাকবে, কিন্তু P1-এর ডান ফর্ক (F2) তখনও ফাঁকা (P2 এখনো শুরুই করেনি) — তাই P1 তার ডান ফর্ক পেয়ে খেয়ে ফেলতে পারবে, তারপর ফর্ক ছেড়ে দিলে P0-ও এগোতে পারবে। ডেডলক তখনই হয় যখন প্রতিটি ফর্কই "কারো না কারো বাম ফর্ক" হিসেবে একসাথে দখল হয়ে যায় — অর্থাৎ পুরো চক্রটি একসাথে বন্ধ হয়ে যায়, যা শুধুমাত্র সবাই (বা এমন একটি সাবসেট যা পুরো বৃত্ত ঘিরে ফেলে) একসাথে শুরু করলেই ঘটে।
প্র ০২ N-1 ফিক্সে কেন ঠিক P4-কে (এবং অন্য কাউকে নয়) বসতে না দেওয়া বেছে নেওয়া হলো? এটা কি নির্দিষ্ট কাউকে হতে হবে?
না, কে বসবে না তা গুরুত্বপূর্ণ নয় — গুরুত্বপূর্ণ শুধু এটাই যে ৫ জনের মধ্যে অন্তত একজন বসবে না। যেই না বসুক, তার দুটো ফর্কই (তার বাম ও তার ডান প্রতিবেশীর কাছে যাওয়া ফর্ক) অন্তত একটি এমন থাকবে যা কারো বাম ফর্ক হিসেবে দাবি করা হয়নি — এটাই pigeonhole যুক্তি, নির্দিষ্ট ব্যক্তির ওপর নির্ভর করে না। কোডে আমরা P4 বেছে নিয়েছি শুধু উদাহরণের সরলতার জন্য।
প্র ০৩ "অ্যাসিমেট্রিক সমাধান" (একজন দার্শনিক আগে ডান ফর্ক তোলে, বাকিরা বাম) কীভাবে ঠিক একই ডেডলক এড়ায়, N-1 ফিক্সের সাথে তুলনা করে বলুন।
অ্যাসিমেট্রিক সমাধানে ধরুন P0 প্রথমে ডান ফর্ক (F1) তোলে, বাকি P1-P4 প্রথমে বাম ফর্ক তোলে। এখন সবাই একসাথে চেষ্টা করলে — P0 চায় F1 (কিন্তু P1 হয়তো ইতিমধ্যে সেটি তার বাম হিসেবে ধরে ফেলেছে, তাই P0 ব্লক হতে পারে), কিন্তু P1 এর ডান ফর্ক (F2) তখনও তার নিজের বাম না হওয়ায় স্বাভাবিক নিয়মে চলবে — প্রতিসাম্য ভেঙে যাওয়ায় অন্তত একটি জায়গায় চক্রটি ভেঙে যায়, তাই পূর্ণ বৃত্তাকার অপেক্ষা তৈরি হতে পারে না। N-1 ফিক্স সংখ্যা কমিয়ে hold-and-wait ভাঙে; অ্যাসিমেট্রিক সমাধান সংখ্যা না কমিয়ে প্রতিসাম্য ভেঙে circular wait নিজেই অসম্ভব করে দেয় — দুটোই বৈধ, ভিন্ন কৌশল।
অনুশীলন
-
পরীক্ষা করুন: ফিক্সের কোড সেলে
seated = [0, 1, 2, 3]-কেseated = [0, 1, 2, 4]করে Run চাপুন (P3 বাদ, P4 রাখা)। এবারও কি সবাই খেতে পারে?হ্যাঁ, এবারও সবাই সফলভাবে খেতে পারবে। P3 না বসলে ফর্ক F3 (P3-এর বাম) কারো বাম ফর্ক হিসেবে দাবি হবে না, তাই P2 (যার ডান ফর্ক F3) সরাসরি সেটি পাবে এবং দুটো ফর্কই পেয়ে খাবে, তারপর ছেড়ে দিলে P1, তারপর P4 এগোতে পারবে (যেহেতু P4-এর বাম ফর্ক F4 ইতিমধ্যে তার নিজের ধরা)। এটি দেখায় — ফিক্সটি নির্দিষ্ট কাউকে বাদ দেওয়ার ওপর নির্ভর করে না, শুধু "কাউকে একজন বাদ" থাকলেই কাজ করে।
-
চিন্তা করুন: যদি ৭ জন দার্শনিক ও ৭টি ফর্ক থাকত (N=7), তাহলে নাইভ সমাধানে ডেডলক ঘটতে কি সবার একসাথে বাম ফর্ক তোলা লাগবে, নাকি কম সংখ্যকেই যথেষ্ট?
পূর্ণ ডেডলকের জন্য পুরো বৃত্তটিকে বন্ধ হতে হবে — অর্থাৎ N=7 হলেও ডেডলক ঘটতে হলে ৭ জনকেই (পুরো চক্র) একসাথে তাদের বাম ফর্ক তুলতে হবে, যাতে প্রতিটি ফর্কই কারো-না-কারো বাম হিসেবে দখল হয়ে যায় এবং কোনো ফাঁকা ফর্ক অবশিষ্ট না থাকে। যদি সাতজনের মধ্যে ছয়জনও একসাথে তোলে (একজন বাদে), তাহলে ঠিক N-1 ফিক্সের মতোই একটি ফর্ক ফাঁকা থাকবে এবং সেই একটি সুযোগ পুরো বৃত্তকে খুলে দেবে — তাই N যা-ই হোক, নীতি একই থাকে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পরের পাঠ: মনিটর ও উচ্চ-স্তরের সিনক্রোনাইজেশন কনস্ট্রাক্ট L22 M5-এর শেষ পাঠ — কীভাবে raw সেমাফোরের ম্যানুয়াল ভুলের ঝুঁকি এড়ানো যায়।
- পূর্বের পাঠ ফিরে দেখুন: রিডার্স-রাইটার্স L20 বাউন্ডেড ওয়েটিং শর্তের আরেকটি বাস্তব রূপ — স্টারভেশন।
- M6-এর প্রথম পাঠ: ডেডলক — চারটি প্রয়োজনীয় শর্ত L23 এই পাঠের ডেডলক দৃষ্টান্তকেই একটি resource-allocation গ্রাফ হিসেবে ঔপচারিকভাবে মডেল করা হবে।
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৬টি পাঠ M6 — ডেডলক প্রিভেনশন, অ্যাভয়ডেন্স (ব্যাংকার'স অ্যালগরিদম), ডিটেকশন ও রিকভারি।