Ada-তে নিরাপদ concurrency ── টাস্ক ও protected object-এর ব্যবহারিক গাইড
· হালনাগাদের তারিখ: · Go Komura · Ada, Concurrency, Tasking, ProtectedObjects, Rendezvous, RealTime, ParallelProgramming, ProgrammingLanguage, সমান্তরাল প্রক্রিয়াকরণ, উচ্চ নির্ভরযোগ্যতা
1. সূচনা ── ভাষায় বসানো concurrency
আধুনিক সফটওয়্যার ডেভেলপমেন্টে concurrency এড়ানো যায় না। তবু অধিকাংশ ভাষায় concurrency «পরে জোড়া»—লাইব্রেরি বা OS ফিচারের উপর নির্ভরশীল, আর ঠিকমতো ব্যবহার করতে গভীর জ্ঞান ও সতর্ক ডিজাইন লাগে।
Ada এই সমস্যার নিজস্ব উত্তর দেয়। ভাষার স্পেসিফিকেশনেই concurrency বসানো আছে।
Ada-এর concurrency মডেল:
- টাস্ক (task) ── স্বাধীনভাবে চলা concurrent একক
- rendezvous ── টাস্কের মধ্যে synchronous যোগাযোগ
- protected object ── ভাষা-পরিচালিত exclusive control
- রিয়েল-টাইম priority ── Annex D রিয়েল-টাইম ফিচার
টাস্ক ও rendezvous 1983-এর Ada 83 থেকেই আছে; protected object ও Annex D রিয়েল-টাইম ফিচার এসেছে Ada 95-এ, তারপর Ada 2005 ও 2012 ধরে এগোয়েছে। Mutex বা semaphore-এর মতো নিচু স্তরের synchronization primitive নয়; বরং ডিজাইনের অভিপ্রায় সরাসরি কোডে লেখা যায়—এটাই Ada concurrency-এর বড় বৈশিষ্ট্য।
এই নিবন্ধে Ada concurrency আটটি ব্যবহারিক কোড উদাহরণ দিয়ে ধাপে ধাপে ব্যাখ্যা করব। প্রতিটি উদাহরণ আলাদা snippet হিসেবে সত্যিই compile ও run করা যায়, নিজের মেশিনে চেষ্টা করা যায়।
নিবন্ধে যে কোড খণ্ড আছে, সেগুলো অধ্যায় অনুযায়ী ফাইলে সাজানো রেফারেন্স সংগ্রহ হিসেবে GitHub-এ প্রকাশিত।
ada-task-concurrency - komurasoft-blog-samples (GitHub)
নিজের মেশিনে চালানো ── বিল্ড ও রান
«compile ও run করা যায়» লিখেছি বলে, তার পদ্ধতিও আগে দেখিয়ে রাখি।
GNAT প্রস্তুত করা
GNAT হলো GCC-এর Ada কম্পাইলার। Linux-এ apt install gnat-13, Windows-এ MSYS2-এর mingw-w64-x86_64-gcc-ada, অথবা Alire (Ada / SPARK প্যাকেজ ম্যানেজার) থেকে নেওয়া যায়।
বিল্ড করে চালানো
প্রতিটি snippet এক ফাইলে একাধিক compilation unit (টাস্ক specification, টাস্ক body, main procedure) রাখে, তাই gnatchop দিয়ে ভাগ করে তারপর gnatmake করতে হয়। gnatchop হলো সেই টুল যা GNAT-এর «unit-এর নাম = ফাইলের নাম» নিয়ম অনুযায়ী ফাইল ভাগ করে।
mkdir work && cd work
gnatchop ../src/snippets/01_hello_task.ada
gnatmake hello_task_demo
./hello_task_demo
ভাগ করার পর main procedure-এর নামই এক্সিকিউটেবলের নাম হয়ে যায়।
অধ্যায় ও ফাইলের মিল
অধ্যায় নম্বর ও ফাইল নম্বর এক ধাপ সরে থাকে (৩য় অধ্যায় 01_)। মিল এই রকম।
| অধ্যায় | ফাইল | এক্সিকিউটেবল | বিষয় |
|---|---|---|---|
| 3 | 01_hello_task.ada |
hello_task_demo |
টাস্কের মৌলিক রূপ |
| 4 | 02_rendezvous_intro.ada |
rendezvous_demo |
rendezvous দিয়ে দুদিকের ডেটা স্থানান্তর |
| 5 | 03_selective_accept.ada |
selective_accept_demo |
selective accept ও সার্ভার টাস্ক |
| 6 | 04_producer_consumer.ada |
producer_consumer_demo |
producer-consumer |
| 7 | 05_protected_counter.ada |
protected_counter_demo |
protected object দিয়ে exclusive control |
| 8 | 06_bounded_buffer.ada |
bounded_buffer_demo |
barrier সহ protected entry (bounded buffer) |
| 9 | 07_timed_entry.ada |
timed_entry_demo |
timeout সহ select কল |
| 10 | 08_task_priorities.ada |
task_priorities_demo |
টাস্ক priority ও রিয়েল-টাইম scheduling |
নিবন্ধের কোড খণ্ডগুলো ব্যাখ্যার জন্য দরকারি অংশ কেটে নেওয়া। এভাবেই চলবে না; হাতে চালাতে উপরের ফাইল ব্যবহার করুন।
এই নিবন্ধের জ্ঞান মানচিত্র
Ada-এর task entry ও accept দিয়ে rendezvous করে বাইরের সঙ্গে সিঙ্ক্রোনাস যোগাযোগ করে, আর selective accept (select স্টেটমেন্ট) একাধিক entry-কে গার্ড শর্তসহ অপেক্ষা করে রাখে এবং or terminate দিয়ে ডেডলকের কারণ হয়ে ওঠা, অপেক্ষা করে থেকে যাওয়া সার্ভার task এড়ানো যায়। protected object ভাষা-পরিচালিত exclusive control; entry-এর barrier শর্ত সত্য না হওয়া পর্যন্ত কলারকে অপেক্ষা করিয়ে শেয়ার করা ডেটায় exclusive access নিশ্চিত করে ডেটা রেস আটকায়, অন্যদিকে protected অপারেশনের ভিতরে delay ইত্যাদি নিষিদ্ধ কাজ করলে bounded error (সীমিত ত্রুটি)-এ পড়ে এবং কিছু ইমপ্লিমেন্টেশনে ডেডলক হতে পারে। অগ্রাধিকার সিলিং প্রোটোকল (Priority Ceiling Protocol) protected object-এ সিলিং অগ্রাধিকার সেট করে priority inversion আটকায়, আর Rate Monotonic Scheduling-এর তাত্ত্বিক পটভূমিতে রিয়েল-টাইম বৈশিষ্ট্য ধরে রাখা হয়। Ravenscar প্রোফাইল task মডেল সীমাবদ্ধ করে static deadlock analysis সম্ভব করে।
flowchart LR
accTitle: Ada-এর task ও protected object-এর জ্ঞান মানচিত্র
accDescr: Ada-এর task যেভাবে rendezvous-এ সিঙ্ক্রোনাস যোগাযোগ করে এবং selective accept ডেডলক এড়িয়ে একাধিক entry-র জন্য অপেক্ষা করে, protected object যেভাবে barrier দিয়ে exclusive control করে ডেটা রেস আটকায়, আর অগ্রাধিকার সিলিং প্রোটোকল ও Ravenscar প্রোফাইল যেভাবে রিয়েল-টাইম বৈশিষ্ট্য ধরে রাখে তা দেখানো চিত্র
ada_task["Ada-এর task (concurrency)"]
protected_object["প্রোটেক্টেড অবজেক্ট (protected object)"]
ada["Ada (প্রোগ্রামিং ভাষা)"]
rendezvous["rendezvous"]
selective_accept["সিলেকটিভ accept (select স্টেটমেন্ট)"]
deadlock["ডেডলক (deadlock)"]
barrier["barrier (protected entry-এর when শর্ত)"]
data_race["ডেটা রেস (data race)"]
erroneous_execution["erroneous execution (ভুল এক্সিকিউশন)"]
bounded_error["bounded error (সীমিত ত্রুটি)"]
priority_ceiling_protocol["প্রায়োরিটি সিলিং প্রোটোকল (Priority Ceiling Protocol)"]
priority_inversion["প্রায়োরিটি ইনভার্সন (priority inversion)"]
task_priority["task প্রায়োরিটি (pragma Priority)"]
rate_monotonic_scheduling["Rate Monotonic Scheduling (RMS)"]
ravenscar_profile["Ravenscar প্রোফাইল"]
ada_task -->|"পূর্বশর্ত"| ada
protected_object -->|"পূর্বশর্ত"| ada
ada_task -->|"ব্যবহার করে"| rendezvous
selective_accept -->|"ব্যবহার করে"| rendezvous
selective_accept -.->|"প্রতিরোধ করে"| deadlock
protected_object -.->|"ব্যবহার করে"| barrier
protected_object -->|"প্রতিরোধ করে"| data_race
data_race -->|"কারণ হতে পারে"| erroneous_execution
bounded_error -.->|"কারণ হতে পারে"| deadlock
protected_object -.->|"কারণ হতে পারে"| bounded_error
priority_ceiling_protocol -->|"প্রতিরোধ করে"| priority_inversion
priority_ceiling_protocol -->|"পূর্বশর্ত"| protected_object
priority_ceiling_protocol -->|"ব্যবহার করে"| task_priority
rate_monotonic_scheduling -->|"ব্যবহার করে"| task_priority
rate_monotonic_scheduling -.->|"প্রস্তাবিত সমাধান"| ada_task
ravenscar_profile -.->|"কমায়"| deadlock
ravenscar_profile -->|"পূর্বশর্ত"| ada_task
ada_task -.->|"কারণ হতে পারে"| priority_inversion
ada_task -.->|"দিয়ে কনফিগার"| task_priority
rendezvous -.->|"কমায়"| deadlock
চিত্রে, টানা রেখা সবসময় সত্য এমন সম্পর্ককে এবং ভাঙা রেখা শর্তসাপেক্ষ সম্পর্ককে নির্দেশ করে (শর্তগুলো বিস্তারিত পাতায় প্রতিটি সম্পর্কের ব্যাখ্যায় আছে)। সম্পর্কের সম্পূর্ণ তালিকা (মোট 20, প্রমাণ ও নিশ্চয়তার মাত্রাসহ) এবং প্রধান ধারণাগুলোর সংজ্ঞা জ্ঞান মানচিত্রের বিস্তারিত পাতায় সংগ্রহ করা আছে (জাপানি ভাষায়)। তথ্য: JSON-LD / Turtle
2. concurrency-এর «বিপদ» ফিরে দেখা
Ada-এর কথায় ঢোকার আগে, «নিরাপদ» concurrency কেন জরুরি তা সংক্ষেপে দেখে নিই।
concurrency-এর পরিচিত বাগগুলোর মধ্যে এগুলো আছে।
- data race: একাধিক thread একই মেমোরি অবস্থানে একসাথে যায়, অন্তত একজন লেখে। ফল undefined।
- deadlock: একাধিক টাস্ক একে অপরের শেষ হওয়ার অপেক্ষা করে, আর কখনো এগোয় না।
- priority inversion: উচ্চ priority টাস্ক নিম্ন priority টাস্কের ধরে রাখা রিসোর্সের অপেক্ষা করে, আর মধ্যম priority টাস্ক নিম্ন priority টাস্ককে preempt করে (চলতে থাকা টাস্ক থামিয়ে অন্য টাস্কে সরে যায়)।
- starvation: কোনো টাস্ক কখনোই তার দরকারি রিসোর্স পায় না।
Ada-এর concurrency মডেল এসব সমস্যার বিরুদ্ধে ভাষার স্তরের প্রতিরোধ দেয়।
data race → protected object exclusive access নিশ্চিত করে
deadlock → rendezvous মডেল গঠনমূলক synchronization দেয়
priority inversion → Priority Ceiling Protocol ভাষায় বসানো হিসেবে পাওয়া যায়
starvation → entry barrier ও queuing policy দিয়ে নিয়ন্ত্রণ
3. টাস্কের ভিত্তি ── স্বাধীন execution একক
Ada-তে concurrency-এর মৌলিক একক টাস্ক (task)। টাস্ক thread-এর মতো, তবে OS thread-এর সাথে এক-এক করে মিলবেই এমন নয়; scheduling পরিচালনা করে Ada runtime।
task Greeter is
entry Start;
end Greeter;
task body Greeter is
begin
accept Start;
Put_Line ("Hello from a task!");
end Greeter;
এই কোডে (01_hello_task.ada) কয়েকটা গুরুত্বপূর্ণ কথা আছে।
টাস্ক ডিক্লেয়ার হলেই আপনাআপনি চলা শুরু করে। Greeter টাস্ক সেই procedure-এর begin-এ চালু হয়, আর accept Start;-এ কলকারীর rendezvous অনুরোধের অপেক্ষা করে।
entry হলো টাস্ক বাইরের দিকে যে ইন্টারফেস খুলে দেয়। কলকারী Greeter.Start; বললে টাস্কের accept Start;-এর সাথে সিঙ্ক হয়। একেই rendezvous বলে।
টাস্ক শেষ হওয়া আপনাআপনি অপেক্ষা করা হয়। main procedure শেষ হলে, তখনও চলতে থাকা টাস্ক থাকলে তাদের সম্পূর্ণতা অন্তর্নিহিতভাবে অপেক্ষা করা হয়। C++-এ std::thread::join ভুলে যাওয়ায় যে crash হয়, তার বিপরীত চিত্র।
রানের উদাহরণ
gnatchop ../src/snippets/01_hello_task.ada
gnatmake hello_task_demo
./hello_task_demo
Main: starting task...
Hello from a task!
Main: task has completed.
এখানে একটা সতর্কতা আছে। accept Start;-এ do ... end ব্লক নেই। অর্থাৎ rendezvous প্রতিষ্ঠিত হওয়ার মুহূর্তে দুজনেই মুক্ত হয়, তারপর পাশাপাশি এগোয়। তাই শেষ দুই লাইনের ক্রম নিশ্চিত নয়। পরিবেশভেদে Main: task has completed. আগেও আসতে পারে। ক্রমও বেঁধে রাখতে চাইলে যে কাজের ক্রম রাখতে চান, সেটা accept Start do ... end Start;-এর do ... end-এর ভিতরে রাখুন। শুধু প্রথম লাইন সবসময় শুরুতে আসে। কারণ Greeter Greeter.Start; না আসা পর্যন্ত accept-এ দাঁড়িয়ে থাকে।
4. rendezvous ── ডেটা হাতে হাতে দেওয়া synchronous যোগাযোগ
Rendezvous শুধু synchronization নয়; দুদিকে ডেটাও দেওয়া-নেওয়া যায়।
task Worker is
entry Compute (X, Y : Integer; Result : out Integer);
end Worker;
task body Worker is
A, B : Integer;
Output : Integer;
begin
accept Compute (X, Y : Integer; Result : out Integer) do
A := X;
B := Y;
Output := A * A + B * B;
Result := Output;
end Compute;
end Worker;
কলকারী এভাবে ব্যবহার করে (02_rendezvous_intro.ada)।
Worker.Compute (3, 4, Answer);
Put_Line ("Main: result = " & Integer'Image (Answer));
এখানকার গুরুত্বপূর্ণ ডিজাইন কথাটা হলো, প্যারামিটার মোড স্পষ্ট করে লেখা।
inমোড: কলকারী থেকে টাস্কে মান পাঠায়outমোড: টাস্ক থেকে কলকারীকে ফল ফেরায়in outমোড: দুদিকই
accept body-র do ... end ব্লকই critical section। এই সময়ে কলকারী block থাকে, টাস্ক অন্য entry নেয় না। কাজ শেষ হলে দুজনেই আবার চলে।
সময়ক্রমে দেখলে অপেক্ষার ছবিটা এমন।
sequenceDiagram
accTitle: Ada rendezvous-এর সময়ক্রম
accDescr: কলকারী ও Worker টাস্ক Compute-এ সিঙ্ক করে, accept body চালায়, তারপর একসাথে আবার চলতে শুরু করে।
participant Main as কলকারী
participant W as Worker টাস্ক
Main->>W: Worker.Compute কল করে
Note over Main: accept-এ পৌঁছানো পর্যন্ত block
W->>W: accept Compute ... do-এ পৌঁছায়
Note over Main,W: rendezvous প্রতিষ্ঠিত / accept body চালু হয়
W-->>Main: out প্যারামিটার Result-এ ফল লেখে
Note over Main,W: end Compute-এ দুজনেই একসাথে আবার চলে
Main->>Main: পরের কাজ
W->>W: পরের কাজ
যে আগে পৌঁছায় সে অপেক্ষা করে। কলকারী আগে এলে accept-এ না পৌঁছানো পর্যন্ত, টাস্ক আগে এলে কেউ কল না করা পর্যন্ত—দুজনেই থেমে থাকে। কে আগে আসুক, do ... end-এর ভিতর সবসময় দুজন জড়ো হয়েই চলে।
Rendezvous-এর বৈশিষ্ট্য গুছিয়ে বললে:
| বৈশিষ্ট্য | ব্যাখ্যা |
|---|---|
| synchronous | কলকারী ও টাস্ক দুজনেই rendezvous পয়েন্টে না পৌঁছানো পর্যন্ত অপেক্ষা করে |
| ডেটা স্থানান্তর | in / out / in out প্যারামিটারে দুদিকে মান পাঠানো যায় |
| exclusive control | accept body চলাকালীন সেই টাস্কের অন্য entry block থাকে |
| গঠনমূলক | কোন entry কখন নেওয়া হবে তা task body-তে স্পষ্ট করে লেখা থাকে |
রানের উদাহরণ
gnatchop ../src/snippets/02_rendezvous_intro.ada
gnatmake rendezvous_demo
./rendezvous_demo
Main: calling Worker.Compute...
Main: result = 25
3 * 3 + 4 * 4 = 25 out প্যারামিটার দিয়ে ফিরে এসেছে। =-এর ডানদিকে একটা ফাঁকা জায়গা পড়ে কারণ Integer-এর 'Image ঋণাত্মক নয় এমন মানের আগে এক অক্ষর ফাঁকা রাখে। ৩য় অধ্যায়ের মতো নয়, এখানে দুই লাইনের ক্রম নিশ্চিত। কারণ Worker.Compute কল end Compute;-এ না পৌঁছানো পর্যন্ত ফেরে না।
5. selective accept ── একাধিক সার্ভিসের অপেক্ষা
আসল সার্ভার টাস্কে নানা ধরনের অনুরোধের অপেক্ষা করতে হয়। Ada-এর select statement এটা ভাষার স্তরে করে।
task Server is
entry Deposit (Amount : Integer);
entry Withdraw (Amount : Integer; Success : out Boolean);
entry Balance (Value : out Integer);
end Server;
task body Server is
Current : Integer := 0;
begin
loop
select
accept Deposit (Amount : Integer) do
Current := Current + Amount;
end Deposit;
or
accept Withdraw (Amount : Integer; Success : out Boolean) do
if Current >= Amount then
Current := Current - Amount;
Success := True;
else
Success := False;
end if;
end Withdraw;
or
accept Balance (Value : out Integer) do
Value := Current;
end Balance;
or
terminate;
end select;
end loop;
end Server;
এই কোডের (03_selective_accept.ada) select-এ একাধিক or শাখা আছে; কল আছে এমন entry-র মধ্যে একটি বেছে নেওয়া হয় (বাছাই implementation-defined)। কোনো entry-ই না ডাকা থাকলে কোনো একটা না আসা পর্যন্ত অপেক্ষা করে।
or terminate; বিশেষ শাখা—«main procedure শেষ, আর এই টাস্কে কেউ entry কল করার সম্ভাবনাও নেই» সেই মুহূর্তে টাস্ক নিরাপদে থামিয়ে দেয়। Deadlock-এর কারণ হয়ে ওঠা «অপেক্ষা করতেই থাকা সার্ভার টাস্ক» সমস্যার Ada-এর নিজস্ব সমাধান।
selective accept-এর শক্তি এই যে guard condition-ও লেখা যায়।
এবার ভিতরে ring buffer রাখা একটি টাস্ক। শুধু select কেটে দেখালে Count বা Head কোথা থেকে এল বোঝা যায় না, তাই declaration থেকে একটানা দেখাচ্ছি। একই ধারণা protected object দিয়ে লেখার রূপ ৮ম অধ্যায়ে আসবে।
task Buffer_Task is
entry Put_Item (Item : Integer);
entry Get_Item (Item : out Integer);
end Buffer_Task;
task body Buffer_Task is
Max : constant := 8;
Data : array (0 .. Max - 1) of Integer;
Head : Integer := 0; -- পরের যে অবস্থান থেকে নেওয়া হবে
Tail : Integer := 0; -- পরের যে অবস্থানে লেখা হবে
Count : Integer := 0; -- বর্তমান উপাদানের সংখ্যা
begin
loop
select
when Count > 0 =>
accept Get_Item (Item : out Integer) do
Item := Data (Head);
Head := (Head + 1) mod Max;
Count := Count - 1;
end Get_Item;
or
when Count < Max =>
accept Put_Item (Item : Integer) do
Data (Tail) := Item;
Tail := (Tail + 1) mod Max;
Count := Count + 1;
end Put_Item;
or
terminate;
end select;
end loop;
end Buffer_Task;
Guard মিথ্যা হলে সেই শাখা তখনকার বাছাই থেকে সরে যায়। এতে «buffer খালি থাকলে Get অপেক্ষা করাবে, ভর্তি থাকলে Put অপেক্ষা করাবে»—এমন নিয়ন্ত্রণ ঘোষণামূলকভাবে লেখা যায়। Head ও Tail-কে mod Max দিয়ে এগোনোই ring buffer-এর মূল কাজ; guard সেই সূচক বৈধ সীমায় থাকবে তাও নিশ্চিত করে।
6. producer-consumer ── rendezvous দিয়ে সিঙ্ক
Rendezvous-এর পরিচিত প্যাটার্ন হিসেবে producer-consumer দেখি।
task Consumer is
entry Deliver (Item : Integer);
end Consumer;
task Producer;
task body Consumer is
Sum : Integer := 0;
begin
for I in 1 .. 5 loop
accept Deliver (Item : Integer) do
Sum := Sum + Item;
end Deliver;
end loop;
end Consumer;
task body Producer is
begin
for I in 1 .. 5 loop
Consumer.Deliver (I);
end loop;
end Producer;
এই প্যাটার্নে (04_producer_consumer.ada) producer প্রতিবার Deliver কল করলেই consumer-এর সাথে সিঙ্ক হয়। Producer খুব তাড়াতাড়ি হলে consumer accept না করা পর্যন্ত অপেক্ষা করে; consumer খুব তাড়াতাড়ি হলে producer-এর পরের কল না আসা পর্যন্ত অপেক্ষা করে। স্বাভাবিক backpressure পড়ে (গ্রহীতা সামলাতে না পারলে sender-এর গতি আপনাআপনি কমে)। কিউ না রেখে শুধু rendezvous-এ buffer overflow-এর চিন্তা ছাড়াই এটা দাঁড়ায়।
7. protected object ── lock ছাড়া exclusive control
টাস্ক যেখানে «সক্রিয় অ্যাক্টর», protected object সেখানে «নিষ্ক্রিয় shared data»-এর ব্যবস্থা।
protected Counter is
procedure Increment;
function Value return Integer;
private
Count : Integer := 0;
end Counter;
protected body Counter is
procedure Increment is
begin
Count := Count + 1;
end Increment;
function Value return Integer is
begin
return Count;
end Value;
end Counter;
Protected object-এর গুরুত্বপূর্ণ নিয়ম এগুলো।
- function শুধু পড়ার জন্য। একাধিক টাস্ক একসাথে function কল করতে পারে।
- procedure পড়া-লেখার জন্য। procedure চলাকালীন অন্য procedureও function-ও block হয়।
- entry barrier সহ। Barrier সত্য না হওয়া পর্যন্ত কলকারী কিউতে অপেক্ষা করে।
এই কোডে (05_protected_counter.ada) তিনটি worker টাস্ক প্রত্যেকে 1,000 বার Increment কল করে। Protected object exclusive control নিশ্চিত করে বলে শেষ counter মান সবসময় 3,000 হয়। Mutex-এর lock-unlock হাতে লিখতে হয় না।
task type Worker (Id : Integer; Rounds : Integer);
task body Worker is
begin
for I in 1 .. Rounds loop
Counter.Increment; -- protected object exclusive access নিশ্চিত করে
end loop;
end Worker;
W1 : Worker (1, 1_000);
W2 : Worker (2, 1_000);
W3 : Worker (3, 1_000);
রানের উদাহরণ
এখানে একটা সমস্যা আছে। main procedure সোজা Counter.Value পড়লে worker তখনও ঘুরতে থাকার মাঝের মান পড়ে ফেলতে পারে। তাই সম্পূর্ণ সংস্করণে (05_protected_counter.ada) সম্পূর্ণতা গোনার procedure ও সবার শেষ হওয়ার অপেক্ষার entry যোগ করা আছে।
protected Counter is
procedure Increment;
procedure Mark_Done;
entry All_Done;
function Value return Integer;
private
Count : Integer := 0;
Done_Count : Integer := 0;
end Counter;
entry All_Done when Done_Count = Num_Workers barrier বসিয়ে, প্রতিটি worker লুপ থেকে বেরিয়ে Counter.Mark_Done; কল করে। main procedure Counter.All_Done;-এ সবার শেষ হওয়া অপেক্ষা করে তারপর মান পড়ে। অপেক্ষার জন্য আলাদা flag variable বা sleep লাগে না।
gnatchop ../src/snippets/05_protected_counter.ada
gnatmake protected_counter_demo
./protected_counter_demo
Final counter value = 3000
যতবারই চালান, 3000। তিনটি টাস্ক মিলিয়ে 3,000 বার Increment কল করে, আর protected object প্রতিবার exclusiveভাবে চালায়।
protected object না থাকলে কী হয়
Protected object-এর মূল্য বুঝতে protection ছাড়া বিপজ্জনক কোড দেখি।
-- ⚠ বিপজ্জনক: shared variable সরাসরি পরিবর্তন করা হচ্ছে
Shared_Counter : Integer := 0;
task body Bad_Worker is
begin
for I in 1 .. 10_000 loop
Shared_Counter := Shared_Counter + 1; -- data race!
end loop;
end Bad_Worker;
Shared_Counter := Shared_Counter + 1 CPU স্তরে «পড়া → যোগ → ফিরিয়ে লেখা»—তিন ধাপ। একাধিক টাস্ক একসাথে চালালে এক টাস্কের যোগফল অন্য টাস্কের read-এ পৌঁছানোর আগে হারিয়ে যেতে পারে, increment drop হয়। আরও, এটা Ada RM 9.10-এর erroneous execution। «Erroneous execution» মানে শুধু «মান সরে গেল» নয়; স্ট্যান্ডার্ডের পরিভাষায় সেই প্রোগ্রামের আচরণ নিয়ে স্ট্যান্ডার্ড আর কিছুই নিশ্চিত করে না। Unsynchronized shared variable-এ একসাথে পড়া-লেখা শুধু শেষ কাউন্ট ভুল হওয়ার মধ্যেই থেমে থাকে না; পুরো প্রোগ্রাম যেকোনো ফল দিতে পারে। দুটি টাস্ক প্রত্যেকে 10,000 বার চালালেও শেষ মান 20,000 হবে—এমন কোনো নিশ্চয়তা নেই।
এই «নিশ্চয়তা নেই» নিজের চোখে দেখতে চাইলে উপরের Bad_Worker সংস্করণ বানিয়ে বারবার চালিয়ে প্রতিবারের শেষ মান লিখে রাখুন। এই নিবন্ধে পরিমাপের সংখ্যা দিই না। Data race-এর ফল CPU, optimization option, রানের timing-এ বদলায়; কোনো এক পরিবেশের একটি সংখ্যাকে «এমনই হয়» বলে দেখালে উল্টে «এতটাই সরে» বলে ভুল মাপকাঠি তৈরি হয়। যাচাই করার কথা «20,000-এর চেয়ে ছোট কোনো নির্দিষ্ট মান বেরোয়» নয়; প্রতি রানে ফল বদলায়, আর একবার সঠিক মান এলেও তার কোনো মানে নেই।
Protected object এই সমস্যা syntax দিয়ে আটকায়। শুধু Counter.Increment; কল করলেই কম্পাইলার ও runtime exclusive control নিশ্চিত করে।
8. protected entry ও barrier ── bounded buffer
Protected object-এ entry যোগ করলে শর্তযুক্ত synchronization সম্ভব হয়। ক্লাসিক bounded buffer দিয়ে দেখি।
type Buffer_Array is array (0 .. Buffer_Size - 1) of Integer;
protected Buf is
entry Put (Item : Integer);
entry Get (Item : out Integer);
private
Data : Buffer_Array;
Head : Integer := 0;
Tail : Integer := 0;
Count : Integer := 0;
end Buf;
protected body Buf is
entry Put (Item : Integer) when Count < Buffer_Size is
begin
Data (Tail) := Item;
Tail := (Tail + 1) mod Buffer_Size;
Count := Count + 1;
end Put;
entry Get (Item : out Integer) when Count > 0 is
begin
Item := Data (Head);
Head := (Head + 1) mod Buffer_Size;
Count := Count - 1;
end Get;
end Buf;
when Count < Buffer_Size হলো barrier। Barrier প্রতি entry কলে মূল্যায়ন হয়; সত্য হলে চলে, মিথ্যা হলে কলকারী টাস্ক কিউতে অপেক্ষা করে। Buffer-এর অবস্থা বদলালেই (অন্য টাস্ক Put বা Get চালালে) অপেক্ষমাণ টাস্কের barrier আবার মূল্যায়ন হয়।
পুনর্মূল্যায়ন কখন হয়, শুধু লেখায় ধরা কঠিন। খালি buffer-এ Get আগে এলে সময়ক্রমে এমন হয়।
sequenceDiagram
accTitle: Protected object-এ barrier পুনর্মূল্যায়ন
accDescr: খালি buffer-এ Get কিউতে অপেক্ষা করে; Put-এর পর barrier আবার মূল্যায়ন হয় এবং Get চলে।
participant C as Consumer টাস্ক
participant B as protected object Buf
participant P as Producer টাস্ক
C->>B: Get কল করে
B->>B: barrier Count 0-এর চেয়ে বড় কি না মূল্যায়ন → মিথ্যা
Note over C: Get-এর entry কিউতে অপেক্ষা
P->>B: Put কল করে
B->>B: barrier Count Buffer_Size-এর কম কি না মূল্যায়ন → সত্য
B->>B: Put-এর body চালায় / Count হয় 1
Note over B: protected operation শেষ হওয়ার মুহূর্তে অপেক্ষমাণ entry-র barrier পুনর্মূল্যায়ন
B->>B: Get-এর barrier সত্য হয়েছে
B-->>C: Get-এর body চালিয়ে Consumer মুক্ত করে
কথাটা হলো, barrier পুনর্মূল্যায়ন protected operation শেষ হওয়ার মুহূর্তে একসাথে হয়। Put-এর body শেষ হওয়ার পর Buf-এর lock ছাড়ার আগে অপেক্ষমাণ entry-র barrier মূল্যায়ন হয়, সত্য হওয়াগুলো তখনই চলে। C-এর condition variable-এর মতো «কেউ signal কল করতে ভুলে গেলে আর জাগে না»—এমন ব্যর্থতা এখানে নেই।
এই প্যাটার্ন (06_bounded_buffer.ada) Ada protected object যেখানে সবচেয়ে চমকায়, তার একটি। C-তে pthread mutex + condition variable দিয়ে যা লিখতে হয় তার সাথে তুলনা করুন।
// C + pthread (Ada-এর সাথে তুলনার জন্য)
pthread_mutex_lock(&mutex);
while (count >= BUFFER_SIZE) { // Ada-এর when-এর সমতুল্য
pthread_cond_wait(¬_full, &mutex); // barrier-এর অপেক্ষা
}
data[tail] = item;
tail = (tail + 1) % BUFFER_SIZE;
count++;
pthread_cond_signal(¬_empty); // অপেক্ষমাণ টাস্ককে জানানো
pthread_mutex_unlock(&mutex);
Ada-তে এসবই when Count < Buffer_Size এক লাইনে এসে যায়। while লুপের শর্ত, signal পাঠানো, lock ছাড়ার timing ভুল—এসব বাগের সুযোগ মিলিয়ে যায়।
9. timeout সহ কল ── অনন্তকাল অপেক্ষা নয়
রিয়েল-টাইম সিস্টেমে «চিরকাল অপেক্ষা» চলে না। Ada select ... or delay দিয়ে timeout দেয়।
select
Slow_Worker.Do_Work (Result);
Put_Line ("Main: work completed");
or
delay until Ada.Real_Time.Clock + Milliseconds (500);
Put_Line ("Main: timeout after 500ms!");
end select;
এই কোডে (07_timed_entry.ada) Slow_Worker delay 2.0 চালাচ্ছে বলে এখনও accept-এ পৌঁছায়নি, তাই কিউতে থাকা entry কল 500ms-এ timeout হয়। (Timeout কাজ করে rendezvous নেওয়ার আগের কিউ অপেক্ষায়; rendezvous নিজে থামিয়ে দেয় না।) delay until পরম সময়ের নির্দেশ; cumulative drift আটকানো রিয়েল-টাইম প্রোগ্রামিংয়ের মৌলিক কৌশল।
Ada conditional entry call-ও দেয়।
select
Server.Process (Item);
else
Put_Line ("Server is busy, will retry later");
end select;
else শাখায় তৎক্ষণাৎ rendezvous না হলে সঙ্গে সঙ্গে বিকল্প কাজে চলে যায়। Polling হাতে লিখতে হয় না।
timeout-এর পরের ডিজাইন ভুলবেন না
Timeout সুবিধাজনক, কিন্তু «অপেক্ষা করা যায়নি তারপর কী করবেন»—এটাই ডিজাইনের মূল কথা। মান সত্যিই ফেলে দেওয়া যায় কি না, আবার চেষ্টা করা উচিত কি না, error হিসেবে উপরে জানানো উচিত কি না—এসব অস্পষ্ট রাখলে প্রোডাকশনে ডেটা হারানো বা সার্ভিস থেমে যাওয়ায় পাল্টায়। Timeout লিখলে timeout-এর পরের দায়ও একই জায়গায় ডিজাইন করুন।
periodic টাস্ক ও delay until
delay until শুধু timeout নয়, periodic execution-এও লাগে। সাধারণ delay 0.1 «কাজের সময় + 0.1 সেকেন্ড»কে period করে তোলে; delay until পরম সময়ে পরের চালুর বিন্দু ঠিক করে, তাই কাজের সময়ের উপর নির্ভর না করে স্থির period রাখা যায়।
loop
Next := Next + Period;
Do_Work;
delay until Next;
end loop;
এই প্যাটার্ন সেন্সর নজরদারি বা কন্ট্রোল লুপ—নির্দিষ্ট period চাই এমন যেকোনো জায়গায় কাজে লাগে।
10. টাস্ক priority ও রিয়েল-টাইম scheduling
Ada-এর রিয়েল-টাইম ফিচার Annex D (Real-Time Systems)-এ সংজ্ঞায়িত। Ada implementation Annex D সাপোর্ট করলে টাস্ক priority ও scheduling policy নির্দিষ্ট করা যায়।
নিজের পরিবেশে চলে কি না যাচাই
Annex D হলো Specialized Needs Annex (নির্দিষ্ট ক্ষেত্রের পরিশিষ্ট)-এর একটি; সাপোর্ট implementation ও চালানোর পরিবেশের উপর নির্ভর করে। হাতে পাওয়া যায় কি না, তিন ধাপে আলাদা করুন।
1. priority-এর সীমা দেখা
with Ada.Text_IO; use Ada.Text_IO;
with System;
procedure Check_Priority is
begin
Put_Line ("Priority range :"
& Integer'Image (System.Priority'First)
& " .."
& Integer'Image (System.Priority'Last));
Put_Line ("Default_Priority :"
& Integer'Image (System.Default_Priority));
end Check_Priority;
System.Priority-এর সীমা ও ডিফল্ট implementation-dependent, তাই নির্দিষ্ট সংখ্যা এখানে দিই না। দেখানো সীমা যথেষ্ট চওড়া হলে pragma Priority (System.Default_Priority + 5)-এর মতো নির্দেশ সেই পরিবেশে অর্থবহ।
2. policy নির্দেশ কম্পাইল হয় কি না দেখা
pragma Task_Dispatching_Policy (FIFO_Within_Priorities);
pragma Locking_Policy (Ceiling_Locking);
এগুলো দিয়ে বিল্ড পাস হলে অন্তত syntax হিসেবে গৃহীত।
3. সত্যিই priority অনুযায়ী চলে কি না দেখা
এখানেই সবচেয়ে বড় ফাঁদ। কম্পাইল পাস হওয়া আর OS scheduler যে priority মেনে চালাবে, এ দুটো আলাদা। Linux বা Windows-এর মতো সাধারণ OS-এ রিয়েল-টাইম priority আসলে scheduler-এ পৌঁছাতে OS-এর অনুমতি সেটিং লাগতে পারে। এই নিবন্ধের স্যাম্পল সংগ্রহের README-তেও লেখা আছে, Annex D পুরোপুরি সাপোর্ট না করা পরিবেশে 08_task_priorities.ada সাধারণ টাস্ক হিসেবে চলে।
অর্থাৎ priority কাজ না করলেও প্রোগ্রাম চলে যায়। Hard real-time চাইলে কাগজের priority ডিজাইনই যথেষ্ট নয়; টার্গেট পরিবেশে সত্যিই ক্রম মেপে যাচাই করার ধাপ লাগে।
task High_Task is
pragma Priority (System.Default_Priority + 5);
end High_Task;
task Low_Task is
pragma Priority (System.Default_Priority);
end Low_Task;
আরও উন্নত সেটিং হিসেবে scheduling policy ও Priority Ceiling Protocol-ও নির্দিষ্ট করা যায়।
pragma Task_Dispatching_Policy (FIFO_Within_Priorities);
pragma Locking_Policy (Ceiling_Locking);
Priority Ceiling Protocol priority inversion আটকানোর প্রোটোকল। প্রতিটি protected object-এ pragma Priority (বা Priority aspect) দিয়ে ceiling priority স্পষ্ট করে সেট করা হয়। কলকারী টাস্কের active priority সেই ceiling ছাড়িয়ে গেলে Program_Error ওঠে। অবজেক্ট lock থাকা অবস্থায় ceiling priority-তেই চলে, তাই মধ্যম priority টাস্ক preempt করতে পারে না।
protected Shared_Data is
pragma Priority (15); -- ceiling priority
procedure Update (Val : Integer);
function Read return Integer;
private
Data : Integer := 0;
end Shared_Data;
এই ফিচারগুলো Rate Monotonic Scheduling (RMS)-এর তাত্ত্বিক পটভূমিতে দাঁড়িয়ে, বিমানের ফ্লাইট কন্ট্রোল বা মেডিকেল ডিভাইসের মতো hard real-time সিস্টেমে প্রমাণিত। RMS «period ছোট টাস্ককে বেশি priority»—এমন fixed-priority পদ্ধতি; periodic টাস্ক সেট deadline রাখতে পারবে কি না আগে থেকেই বিশ্লেষণ করা যায় বলে hard real-time-এ গুরুত্ব পায়।
11. বাস্তব কাজের ডিজাইন নির্দেশিকা
এ পর্যন্ত টাস্ক ও protected object-এর মৌলিক syntax দেখেছি। শেষে, কাজে Ada concurrency ব্যবহার করতে যে ডিজাইন কথাগুলো মাথায় রাখা দরকার, সেগুলো গুছিয়ে নিই।
protected object-এ যা করা যাবে না
Protected object-এর ভিতরে শুধু state সংক্ষেপে আপডেট করুন, ভারী কাজ বাইরে চালান—এটাই নিয়ম। Protected object-এর operation ভিতরে exclusive control-এ থাকে বলে, সেখানে দীর্ঘক্ষণ block করলে সেই অবজেক্ট ব্যবহার করা অন্য সব টাস্ক থেমে যায়।
নির্দিষ্ট করে এড়ানো উচিত:
delayবা সময়সাপেক্ষ I/O- অন্য protected object-এ জটিল কল
- বাইরের লাইব্রেরির ভারী কল
আরও, protected operation-এর ভিতরে delay বা নির্দিষ্ট I/O শুধু পারফরম্যান্স সমস্যা নয়; Ada স্ট্যান্ডার্ডে bounded error। Bounded error মানে «সম্ভাব্য ফলের সীমা স্ট্যান্ডার্ড বলে, কিন্তু সীমার কোনটা হবে তা স্থির নয়»। Erroneous execution-এর মতো সীমাহীন নয়, তবু ঠিকমতো চলবে এমন নিশ্চয়তাও নেই। বাস্তবে implementation অনুযায়ী Program_Error উঠতে পারে বা deadlock হতে পারে, তাই «কমানো» নয়, পুরোপুরি বাদ দিতে হয়।
ভালো ডিজাইন হলো «protected object থেকে দরকারি মান অল্প সময়ে নেওয়া → বাইরে ভারী হিসাব বা I/O চালানো → শুধু ফলটা protected object-এ অল্প সময়ে ফিরিয়ে লেখা»।
barrier শর্ত সরল রাখুন
entry ... when <condition>-এর barrier শক্তিশালী, কিন্তু জটিল হয়ে গেলে পড়া কঠিন হয়, আর টাস্ক কেন মুক্ত হচ্ছে না খুঁজে বের করাও কঠিন হয়।
when Count < Buffer_Size বা when Used > 0-এর মতো, অবস্থার মানে একনজরে বোঝা যায় এমন স্তরই আদর্শ। একাধিক শর্ত লাগলে enumeration দিয়ে অবস্থা লিখে barrier when State = Running-এর মতো অবস্থার নামে পড়ার যোগ্য করার কথা ভাবুন।
টাস্কের exception ও থামা
টাস্কের ভিতরে exception উঠলে কী করবেন, তা আগে থেকে স্পষ্ট করে রাখতে হয়। অন্তত task body-র সবচেয়ে উপরে exception ধরে কী ঘটেছে লিখে রাখুন।
আরও জরুরি exception-এর পরের ডিজাইন। সেই টাস্ক থামলে সিস্টেম চলতে পারবে কি না, আবার চালু করা যাবে কি না, অন্য টাস্ককে কীভাবে জানাবেন, shared state কীভাবে নিরাপদ অবস্থায় ফেরাবেন—এসব প্রশ্নের উত্তর রাখা দরকার। Ada-তে exception mechanism ভাষায় আছে, কিন্তু exception-এর পরের নিরাপত্তা অ্যাপ্লিকেশন ডিজাইনের দায়।
ছোট ডিজাইন চেকলিস্ট
| দৃষ্টিভঙ্গি | যাচাই করার কথা |
|---|---|
| shared state | protected object-এর ভিতরে আটকানো কি না। বাইরে থেকে সরাসরি ছোঁয়া হয়নি তো |
| protected operation | ছোট কি না। ভিতরে block করছে না তো |
| entry | barrier সরল কি না। অপেক্ষা করতেই থাকার সম্ভাবনা নেই তো। timeout নীতি আছে কি না |
| টাস্কের আয়ু | শেষ হওয়ার শর্ত স্পষ্ট কি না। exception-এর নীতি আছে কি না |
| periodic কাজ | delay-এর বদলে delay until ভেবেছেন কি না |
concurrency-এ «সম্ভবত ঠিক আছে» সবচেয়ে বিপজ্জনক। Shared state, অপেক্ষার শর্ত, শেষ হওয়ার শর্ত, exception নীতি কোডে স্পষ্ট করে রাখাই নিরাপদ concurrency-এর প্রথম ধাপ।
12. সারসংক্ষেপ ── concurrency-কে «ব্যাকরণ» করা ভাষা
Ada-এর concurrency মডেল অন্য ভাষা থেকে আলাদা এই কারণে যে, নিরাপদ concurrency «পরে জোড়া best practice» নয়, «ব্যাকরণ» হিসেবে বসানো।
| যা করতে চান | Ada-এর ব্যাকরণ |
|---|---|
| স্বাধীন execution একক | task / task body |
| synchronous যোগাযোগ | entry / accept |
| একাধিক অনুরোধের অপেক্ষা | select / or / else |
| exclusive control | protected / function / procedure |
| শর্তযুক্ত synchronization | entry ... when <barrier> |
| timeout | or delay until <time> |
| priority নিয়ন্ত্রণ | pragma Priority |
এই construct কম্পাইলার যাচাই করে। উদাহরণ: protected object-এর function-এ সেই অবজেক্টের নিজের private উপাদান বদলাতে চাইলে compile error হয়। Protected operation শেষ হলে অপেক্ষমাণ entry-র barrier আপনাআপনি আবার মূল্যায়ন হয়—হাতে signal পাঠাতে হয় না।
«type system যেমন memory safety নিশ্চিত করে,
Ada-এর concurrency syntax synchronization-এর নিরাপত্তা নিশ্চিত করে»
এই নিবন্ধের আটটি কোড উদাহরণ টাস্ক, rendezvous, protected object ও রিয়েল-টাইম ফিচারের ব্যবহারিক পরিচিতি। এগুলো নিজের মেশিনে চালিয়ে নিচের উন্নত বিষয়ও চেষ্টা করুন।
- Ravenscar profile: উচ্চ নির্ভরযোগ্য রিয়েল-টাইম সিস্টেমের জন্য টাস্ক সীমাবদ্ধতার profile। সীমিত টাস্ক মডেলে static deadlock বিশ্লেষণ সম্ভব হয়।
- Ada 2022-এর parallel block:
parallel ... dosyntax দিয়ে data-parallel প্রক্রিয়াকরণ। - SPARK-এর সাথে যুক্ত করা: concurrent প্রোগ্রামের আচরণ formal verification (Ravenscar profile-এর নিচে GNATprove সাপোর্ট করে)।
তবু «Ada ব্যবহার করলেই নিরাপদ» নয়
শেষে একটা জরুরি সতর্কতা। Ada-এর concurrency syntax শক্তিশালী, কিন্তু Ada ব্যবহার করলেই আপনাআপনি নিরাপদ হয়ে যায় না। Shared data protected object-এ না রেখে সরাসরি ছোঁয়া, protected object-এর ভিতরে দীর্ঘক্ষণ block করা, একাধিক protected object জটিলভাবে ডাকা—এমন ডিজাইন ভুল Ada-তেও হয়।
ভাষার ফিচার এমনভাবে তৈরি যে «বিপজ্জনকভাবে লিখতে হলে ইচ্ছাকৃত চেষ্টা লাগে», কিন্তু সঠিক ডিজাইন নিজে করে দেয় না। Ada-এর আসল মূল্য এই যে নিরাপত্তার আলোচনা কোডের কাছে নিয়ে আসা যায়—«এই state protected কি না», «এই টাস্ক কখন শেষ হয়», «এই entry কোন শর্তে অপেক্ষা করে»—এমন প্রশ্ন syntax হিসেবে কোডে রেখে দেওয়া যায়।
Type দিয়ে ডিজাইন বলার Ada-এর ধারণা concurrency-তেও একই রকম। নিরাপদ concurrency lock সাবধানে ধরা থেকে শুরু হয় না; বিপজ্জনক shared state খোলা অবস্থায় না রাখা থেকে শুরু হয়।
«concurrency কঠিন»—এই প্রচলিত ধারণার জবাবে Ada বলে, «সঠিক syntax বেছে নিলে নিরাপত্তা কম্পাইলার নিশ্চিত করে»। সেই ডিজাইন চিন্তা আজকের Rust বা Pony-তেও মিলে, কিন্তু Ada সেটা ৪০ বছর ধরে ভাষার স্পেসিফিকেশনে রেখে এসেছে।
সম্পর্কিত নিবন্ধ
কাছাকাছি বিষয়ে গভীরে যেতে একই ট্যাগযুক্ত সাম্প্রতিক নিবন্ধ।
Ada দিয়ে রিয়েল-টাইম সিস্টেম প্রোগ্রামিং ── priority, period ও execution time নিয়ন্ত্রণ বাস্তবে
Ada-এর Annex D (রিয়েল-টাইম সিস্টেম) আটটি ব্যবহারিক কোড উদাহরণ দিয়ে শেখা যায়। টাস্ক priority, Ceiling_Locking, delay until দিয়ে period...
Ada-তে Generic Programming — Type দিয়ে contract লিখে zero-cost reuse
Ada-র generic programming-কে generic subprogram, generic package, formal subprogram, type category ও বাস্তব ডিজাইন নির্দেশিকা পর্যন্ত ধার...
Windows ভার্চুয়ালাইজেশনের গভীরতা (পর্ব ৩) — সেকেন্ডে বুট হওয়া ভার্চুয়াল মেশিন: WSL2, Windows Sandbox ও কন্টেইনার এত হালকা কেন
WSL2 ও Windows Sandbox সেকেন্ডে শুরু হয়ে এত হালকা মনে হয় কেন? এই নিবন্ধ ডায়নামিক বেস ইমেজ ও ডাইরেক্ট ম্যাপ থেকে ডায়নামিক মেমরি বরাদ্দ...
Windows ভার্চুয়ালাইজেশনের গভীরতা (পর্ব ২) — কার্নেলও দেখতে পায় না এমন মেমরি: VBS, HVCI ও Credential Guard কীভাবে কাজ করে
সামঞ্জস্যপূর্ণ হার্ডওয়্যারে ক্লিন ইনস্টলে VBS ডিফল্টে চালু থাকে এবং হাইপারভাইজার ও SLAT দিয়ে কার্নেলের চেয়ে শক্তিশালী আইসোলেশন তৈরি কর...
Windows ভার্চুয়ালাইজেশনের গভীরতা (পর্ব ১) — আপনার Windows আসলে কোথায় চলছে? হাইপারভাইজার ও পার্টিশন
Hyper-V চালু করলে হোস্ট Windows নিজেই রুট পার্টিশন হিসেবে হাইপারভাইজারের উপর চলে। এই নিবন্ধ VT-x, SLAT ও VMBus-এর ভূমিকা দিয়ে ভার্চুয়াল...
সম্পর্কিত বিষয়
এই পৃষ্ঠাগুলো বিষয়টিকে সেবা ও সিদ্ধান্তের বৃহত্তর প্রেক্ষাপটে স্থাপন করে।
Windows-এর প্রযুক্তিগত বিষয়
Windows ডেভেলপমেন্ট, বাগ তদন্ত ও বিদ্যমান সম্পদ ব্যবহারের প্রবেশদ্বার।
প্রায়শ জিজ্ঞাসিত প্রশ্ন
এই নিবন্ধের বিষয়ে পরামর্শে প্রায়ই আসা প্রশ্ন।
- Ada-এর টাস্ক কী?
- টাস্ক হলো Ada-তে concurrency-এর মৌলিক একক। Thread-এর মতো, তবে OS thread-এর সাথে এক-এক করে মিলবেই এমন নয়; scheduling পরিচালনা করে Ada runtime। টাস্ক ডিক্লেয়ার হলেই আপনাআপনি চলা শুরু করে, আর main procedure শেষ হলে তখনও চলতে থাকা টাস্কের সম্পূর্ণতা অন্তর্নিহিতভাবে অপেক্ষা করা হয়। বাইরের সাথে entry দিয়ে rendezvous-এ synchronous যোগাযোগ হয়। টাস্ক ও rendezvous 1983-এর Ada 83 থেকেই ভাষার স্পেসিফিকেশনে আছে।
- Ada-এর protected object mutex থেকে কীভাবে আলাদা?
- Protected object ভাষা-পরিচালিত exclusive control; lock ও unlock হাতে লিখতে হয় না। function শুধু পড়ার জন্য এবং একাধিক টাস্ক একসাথে কল করতে পারে; procedure পড়া-লেখার জন্য এবং চলাকালীন অন্য কল block হয়; entry barrier সত্য না হওয়া পর্যন্ত কলকারীকে কিউতে রাখে। C-তে pthread mutex ও condition variable জোড়া দিয়ে যে bounded buffer লেখা হয়, Ada-তে তা «when Count < Buffer_Size»-এর মতো এক লাইনের barrier-এ গুছিয়ে আসে।
- Ada-এর rendezvous কীভাবে কাজ করে?
- Rendezvous টাস্কের মধ্যে synchronous যোগাযোগ। কলকারীর entry কল ও টাস্কের accept statement দুজনেই rendezvous পয়েন্টে না পৌঁছানো পর্যন্ত একে অপরের অপেক্ষা করে। in/out/in out প্যারামিটার মোডে দুদিকেই ডেটা দেওয়া-নেওয়া যায়। accept body-র do…end ব্লকই critical section; চলাকালীন কলকারী block থাকে এবং টাস্ক অন্য entry নেয় না। select-এর সাথে মিলিয়ে একাধিক entry-র অপেক্ষা, timeout ও guard condition ঘোষণামূলকভাবে লেখা যায়।
- Protected object-এর ভিতরে কী করা যাবে না?
- delay, সময়সাপেক্ষ I/O, বাইরের লাইব্রেরির ভারী কল—দীর্ঘক্ষণ block করে এমন কাজ। Protected operation-এর ভিতরে delay বা নির্দিষ্ট I/O Ada স্ট্যান্ডার্ডে bounded error; implementation অনুযায়ী Program_Error উঠতে পারে বা deadlock হতে পারে, তাই পুরোপুরি বাদ দিতে হয়। শুধু state সংক্ষেপে আপডেট করুন; ভারী হিসাব ও I/O protected object-এর বাইরে চালিয়ে ফলটাই অল্প সময়ে ফিরিয়ে লিখুন—এটাই নিয়ম।