Spurious Wakeup — কন্ডিশন ভেরিয়েবল কেন "নোটিফাই না হয়েও" জাগে এবং Windows-এ সঠিকভাবে কীভাবে অপেক্ষা করবেন
· Go Komura · Windows, মাল্টিথ্রেডিং, কন্ডিশন ভেরিয়েবল, সিঙ্ক্রোনাইজেশন, C++, C#, Win32 API, সমস্যা সমাধান
“কিউতে ডেটা রেখে অপেক্ষায় থাকা ওয়ার্কার থ্রেড জাগালাম। ছয় মাস চলছিল, তারপর একদিন খালি কিউ পড়তে গিয়ে ক্র্যাশ।” “নোটিফিকেশন পাঠাচ্ছি, কিন্তু মাঝে মাঝে একটি থ্রেড জাগেই না।” — মাল্টিথ্রেডেড রেন্ডেজভাস চলছে বলে মনে হয়, আর শুধু বিরলে দেখা বাগের প্রজননক্ষেত্র। এই ধরনের অনুসন্ধান প্রায়ই পড়ে সেই কোডে যা কন্ডিশন ভেরিয়েবলের wait-কে if-এ মুড়ে। আর তার পেছনে বসে spurious wakeup — নোটিফিকেশন না পেয়েই wait থেকে ফেরার ঘটনা।
“কেউ নোটিফাই করেনি তবু জাগে” বাস্তবায়নের ত্রুটি মনে হয়, কিন্তু Win32, C++ ও POSIX সবগুলো ডকুমেন্টেশন বা স্ট্যান্ডার্ডে এই আচরণ লেখে, আর .NET-এর Monitor ডিজাইন করা হয়েছে “একবার জাগলে শর্ত আবার যাচাই করুন” ধরে। সেই আচরণ কেন অনুমোদিত? Windows-এ কোন স্তরে ঘটে? আর ওয়েট কীভাবে লিখলে কখনো ধরা পড়বেন না? Windows-এ ব্যবসায়িক অ্যাপ ও যন্ত্র-নিয়ন্ত্রণ সফটওয়্যার লেখা ডেভেলপারদের লক্ষ্য করে এই নিবন্ধ প্রাথমিক উৎস থেকে spurious wakeup আসলে কী খুলে, আর সঠিক ওয়েট Win32 (C), C++ ও C#-এ সিদ্ধ করে।
১. আগে উপসংহার
- কন্ডিশন ভেরিয়েবলের
waitনোটিফিকেশন না এলেও ফিরতে পারে। Win32-এর অফিসিয়াল ডকুমেন্টেশন বলে কন্ডিশন ভেরিয়েবল spurious wakeup (স্পষ্ট জাগানোর সাথে বাঁধা নয় এমন জাগরণ) ও stolen wakeup (জাগা থ্রেডের আগে অন্য থ্রেড শর্ত খাওয়া)-এর অধীন।1 - তাই ওয়েট সবসময় “while লুপ প্লাস শর্ত আবার যাচাই” হিসেবে লিখতে হয়।
ifদিয়ে একবার যাচাই করে তারপরwaitকরা কোড চলছে বলে মনে হয়, আর শুধু বিরলে পুনরুৎপাদন হয় এমন বাগ লুকিয়ে রাখে।12 - এটি Windows-নির্দিষ্ট খেয়াল নয়; POSIX ও C++ স্ট্যান্ডার্ড একই কথা বলে। “কখনোই spuriousভাবে জাগবে না” এমন বাস্তবায়ন প্রতিটি কন্ডিশন-ভেরিয়েবল অপারেশন ধীর করত, তাই ওয়েটার আবার যাচাই করবে ধরে জাগরণ অনুমোদিত।34
- C++-এ প্রেডিকেট আকার
wait(lock, pred)লাইব্রেরিকে লুপ চালায়। সেই আকার কার্যতwhile (!pred()) wait(lock);চালায়। নতুন কোডের ডিফল্ট।5 - C#-এর
Monitor.Wait-এ একই শৃঙ্খলা দরকার। জাগা ও লক আবার অর্জনের ফাঁকে শর্ত খাওয়া যেতে পারে, তাইwhile-এ শর্ত আবার যাচাই করেWait-এ ফিরুন।6 - একই লকের নিচে শর্ত আপডেট ও যাচাই করুন। লকের বাইরে শর্ত দেখে তারপর
wait-এ ঢুকলে নোটিফিকেশন ফাঁক দিয়ে চলে যেতে পারে — lost wakeup।1 - কন্ডিশন ভেরিয়েবলের “এখন যে অপেক্ষা করছে তাকে জাগাও” ক্ষণস্থায়ী নোটিফিকেশন ইভেন্টের পালস দিয়ে নকল করবেন না। বিশেষ করে
PulseEventকার্নেল-মোড APC সংক্ষেপে ওয়েট তুলে দিলে সেই মুহূর্তে নোটিফিকেশন মিস করতে পারে, আর Microsoft নিজেই স্পষ্ট বলে “অনির্ভরযোগ্য, ব্যবহার করবেন না, বদলে কন্ডিশন ভেরিয়েবল ব্যবহার করুন”।7
এর পর এই উপসংহারকে সমর্থন করা যন্ত্র ক্রমে ঘুরে দেখা।
২. Spurious wakeup কী — জাগা মানে শর্ত ধরে না
কন্ডিশন ভেরিয়েবল সিঙ্ক্রোনাইজেশন প্রিমিটিভ “কোনো শর্ত না ধরা পর্যন্ত থ্রেডকে ঘুম পাড়ানো, আর ধরলে জাগানো”। Win32-এ সেটা CONDITION_VARIABLE স্ট্রাকচার সাথে SleepConditionVariableCS / SleepConditionVariableSRW (ওয়েট) ও WakeConditionVariable / WakeAllConditionVariable (নোটিফাই)। ওয়েট API আপনি যে লক ধরে আছেন (ক্রিটিক্যাল সেকশন বা SRW লক) পরমাণুভাবে ছেড়ে ঘুমায়, আর জাগরণে ফেরার আগে লক আবার অর্জন করে।1
প্রশ্ন হলো “wait থেকে ফিরেছি” এই তথ্য আসলে কী মানে। সরলভাবে ভাবতে ইচ্ছে করে “নোটিফিকেশন এসেছে = শর্ত ধরে”, কিন্তু বাস্তবে তিন ক্ষেত্রে wait ফেরে।
| ক্ষেত্র | নোটিফিকেশন | ফেরার সময় শর্ত |
|---|---|---|
| প্রকৃত জাগরণ | হ্যাঁ | প্রায়ই পূর্ণ, কিন্তু নিশ্চিত নয় |
| Spurious wakeup | আপনার উদ্দেশ্যে কোনোটাই নয় | এখনও অপূর্ণ |
| Stolen wakeup | হ্যাঁ | অন্য থ্রেড আগে খেয়েছে; অপূর্ণ |
flowchart TB
accTitle: wait যে তিন ক্ষেত্রে ফেরে
accDescr: কন্ডিশন ভেরিয়েবল ওয়েট শুধু প্রকৃত নোটিফিকেশন থেকে নয়, নোটিফিকেশনহীন spurious wakeup ও নোটিফিকেশন এসেও শর্ত আগে খাওয়া stolen wakeup থেকেও ফিরতে পারে, তাই প্রতি ক্ষেত্রে শর্ত আবার যাচাই দরকার
w["wait থেকে ফিরেছে"] --> a["প্রকৃত নোটিফিকেশন"]
w --> b["Spurious wakeup (নোটিফিকেশন নেই)"]
w --> c["Stolen wakeup (শর্ত ইতিমধ্যে খাওয়া)"]
a --> r["শর্ত আবার যাচাই, তারপর এগোান"]
b --> r
c --> r
চিত্র ১: wait থেকে ফেরার তিন পথ, আর কলার কোনটা নিয়েছে বলতে পারে না, তাই সবসময় শর্ত আবার যাচাই করতে হয়।
Spurious wakeup এই দ্বিতীয় ক্ষেত্র — ওয়েট API আপনাকে জাগানোর উদ্দেশ্যে স্পষ্ট নোটিফিকেশনের সাথে বাঁধা না হয়ে ফেরার ঘটনা। সিস্টেমে কোথাও WakeConditionVariable কখনো ডাকা হয়নি এমন পরিস্থিতিতেই সীমাবদ্ধ নয়। উদাহরণস্বরূপ, উচ্চ লোডে নোটিফিকেশন সংক্ষিপ্ত ঝাঁকে এলে বাস্তবায়ন অতিরিক্ত অপেক্ষায় থাকা থ্রেড ব্যাচে জাগাতে পারে, আর সংশ্লিষ্ট নোটিফিকেশন নেই সেই পাশ থেকে সেটাও spurious wakeup। Microsoft Learn-এর কন্ডিশন-ভেরিয়েবল পাতা স্পষ্ট বলে: “Condition variables are subject to spurious wakeups (those not associated with an explicit wake) and stolen wakeups (another thread manages to run before the woken thread). Therefore, you should recheck a predicate (typically in a while loop) after a wait operation returns.”1
গুরুত্বপূর্ণ বিষয় কলার তিন ক্ষেত্রের কোন দিয়ে ফিরেছে বলতে পারে না। বলা না গেলে একটিই কৌশল পাওয়া যায়: প্রতিবার ফিরে যে শর্তের অপেক্ষা করছিলেন সেটাই যাচাই করুন, আর না ধরলে আবার ঘুমান। সেটাই “ওয়েটকে while-এ মুড়ান” লোহার নিয়মের আসল বিষয়। উল্টো করে বললে, সেই নিয়ম রাখলে তিন ক্ষেত্রের যেকোনোটা জাগালেও কোড সঠিক।
৩. স্পেসিফিকেশন কেন অনুমোদন করে — সঠিক নোটিফিকেশন ব্যয়বহুল
“নোটিফাই না হয়ে জাগা শুধু অগোছালো বাস্তবায়ন, তাই না?” ন্যায্য প্রশ্ন। আসলে কখনো spuriousভাবে জাগে না এমন বাস্তবায়ন গড়া তাত্ত্বিকভাবে সম্ভব। তবু POSIX, Windows ও C++ স্ট্যান্ডার্ড সব “ঘটতে পারে” পাশে নেমেছে। কারণ POSIX-এর pthread_cond_wait-এর Rationale-এ (The Open Group Base Specifications) স্পষ্ট বলা।3
প্রথম কারণ পারফরম্যান্স। “নির্ভরযোগ্যভাবে ঠিক একটি থ্রেড জাগাও” কঠোরভাবে বাস্তবায়ন করতে চাইলে, বিশেষ করে মাল্টিপ্রসেসরে, প্রতিটি কন্ডিশন-ভেরিয়েবল অপারেশনে অতিরিক্ত সিঙ্ক্রোনাইজেশন খরচ যোগ হয়। নোটিফিকেশন ও জাগরণের মাঝে শিডিউলার বসে, আর ইন্টারাপ্ট ও প্রিএম্পশনের সময় অনুযায়ী “জাগাতে চেয়েছিলেন তার আগে অন্য থ্রেড চলা” এড়ানো যায় না। সেটা সম্পূর্ণ সিল করার খরচ সবাইকে দেওয়া, কন্ডিশন ভেরিয়েবল দ্রুত রাখতে, “মাঝে মাঝে অতিরিক্ত জাগাতে পারেন” মেনে নেওয়ার চেয়ে খারাপ।
দ্বিতীয় কারণ এই পর্যবেক্ষণ যে এই ট্রেড-অফ অ্যাপ ভাঙে না — আসলে আরও মজবুত করে। Spurious wakeup অনুমোদিত বলে সঠিক কোড সবসময় প্রেডিকেট (যে শর্তের অপেক্ষা) যাচাই করা লুপ লেখে। POSIX-এর Rationale বলে এই লুপ বাধ্য করলে কোড স্ব-নথিভুক্ত ও আরও মজবুত হয়।3 লুপ থাকলে নোটিফিকেশনের অর্থ “শর্ত ধরে তার নিশ্চয়তা” থেকে নেমে “শর্ত বদলে থাকতে পারে এমন ইঙ্গিত” হয়, আর ওয়েট পাশ নোটিফায়ারের মাপসই ডিজাইন বদল (অতিরিক্ত জাগানো, ব্যাচে জাগানো ইত্যাদি) সহ্য করে।
Stolen wakeup আরও কাঠামোগত বিষয়। নোটিফায়ার WakeConditionVariable ডাকা ও জাগা থ্রেড লক আবার অর্জন করে wait থেকে ফেরার মাঝে সবসময় সময়ের ফাঁক থাকে। তৃতীয় থ্রেড সেই ফাঁকে লক নিতে পারলে শর্ত (কিউয়ের বিষয়বস্তু ইত্যাদি) আগে খেতে পারে। সেটা বাস্তবায়ন যতই ঘষেও মুছে ফেলা যায় না, কারণ তা কন্ডিশন-ভেরিয়েবল হাতিয়ারের আকৃতি থেকেই আসে।
sequenceDiagram
accTitle: Stolen wakeup-এর টাইমলাইন
accDescr: প্রডিউসার কিউতে একটি আইটেম রেখে অপেক্ষায় থাকা কনজিউমার A-কে জাগায়, কিন্তু A লক আবার অর্জন করার আগে কনজিউমার B লক নিয়ে সেই একটি আইটেম নেয়, তাই A জাগার সময় কিউ খালি
participant A as কনজিউমার A (অপেক্ষায়)
participant P as প্রডিউসার
participant B as কনজিউমার B
P->>P: কিউতে একটি আইটেম যোগ
P->>A: WakeConditionVariable
Note over A: জেগেছে, লক আবার অর্জনের অপেক্ষা
B->>B: লক নিয়ে একটি আইটেম নেয়
A->>A: লক আবার অর্জন করে wait থেকে ফেরে
Note over A: কিউ খালি (stolen)
A->>A: while লুপে আবার যাচাই করে আবার অপেক্ষা
চিত্র ২: নোটিফিকেশন ও জাগরণের সময়ের ফাঁকে তৃতীয় থ্রেড শর্ত খায় এমন “stolen wakeup” যেকোনো বাস্তবায়নে ঘটতে পারে।
অর্থাৎ OS সম্পূর্ণ spurious wakeup নির্মূল করলেও, stolen wakeup থাকলে “জাগলাম = শর্ত ধরে” লেখা যায় না। ওয়েটারের আবার-যাচাই লুপ যাই হোক দরকার, আর সেটা থাকলে spurious wakeup অনুমোদন করে বাস্তবায়ন দ্রুত রাখা সস্তা — সেই ডিজাইন-বিচার কন্ডিশন ভেরিয়েবল দশক ধরে বহন করেছে।
৪. Windows-এ কোন স্তরে দেখা যায়
Windows সিঙ্ক্রোনাইজেশন প্রিমিটিভের যে স্তরই ব্যবহার করুন এই বৈশিষ্ট্য মুখ দেখায়। যে স্তরের API-এ লিখুন না কেন পালানো যায় না সেই অনুভূতি পেতে প্রতিনিধি স্তরগুলো দেখব।
Win32 কন্ডিশন ভেরিয়েবল (CONDITION_VARIABLE) ইতিমধ্যে বলা হয়েছে, SleepConditionVariableCS / SleepConditionVariableSRW-এ spurious wakeup ও stolen wakeup দুটোর অধীন নথিভুক্ত, আর while লুপে প্রেডিকেট আবার যাচাই বাধ্যতামূলক।2 অফিসিয়াল ব্যবহার নমুনা (প্রডিউসার–কনজিউমার কিউ)ও ওয়েটকে while লুপের ভেতরে লেখে।8
আরও নিচের স্তরের WaitOnAddress কন্ডিশন ভেরিয়েবলের চেয়ে আরও আদিম ওয়েট API: “দেওয়া অ্যাড্রেসের মান বদল না হওয়া পর্যন্ত অপেক্ষা” (Windows 8 ও পরের)। এই প্রায়-তলার API-এর ডকুমেন্টেশনও বলে “অ্যাড্রেস সিগন্যাল হলে ফেরা নিশ্চিত, কিন্তু অন্য কারণেও ফেরা অনুমোদিত”, আর তাড়াতাড়ি জাগার উদাহরণ হিসেবে কম-মেমরি অবস্থা, একই অ্যাড্রেসের আগের জাগরণ পরিত্যাগ, ও চেকড বিল্ড চালানো তালিকা করে। তাই ডকুমেন্টেশনের নিজের ব্যবহার নমুনা “মান আবার তুলনা করা while লুপ” আকারে।9
C++-এর std::condition_variable একই। MSVC-এর ডকুমেন্টেশন প্রেডিকেটহীন wait নিয়ে বলে “notify_one / notify_all কলে সিগন্যাল না হওয়া পর্যন্ত ব্লক করে। Spuriousভাবেও জাগতে পারে”, আর প্রেডিকেট আকার wait(lock, pred) কার্যত নিচের কোড চালায় বলে ব্যাখ্যা করে।5
while (!Pred())
wait(Lck);
অর্থাৎ C++-এ সুপারিশকৃত প্রেডিকেট-আকারের wait অন্য কিছু নয়, এই নিবন্ধে বর্ণিত “while-এ মুড়ানো” লাইব্রেরি আপনার হাত থেকে নেওয়া। cppreferenceও বলে প্রেডিকেটহীন wait spuriousভাবে আনব্লক হতে পারে।4
.NET-এর Monitor.Wait / Pulse-এর ওয়েটিং কিউ ও রেডি কিউয়ের নিজস্ব কিউ কাঠামো আছে, কিন্তু শৃঙ্খলা বদলায় না। Pulse / PulseAll-এ জাগা থ্রেড রেডি কিউতে যায় আর লক আবার অর্জন করতে পারার ক্রমে Wait থেকে ফেরে। লক আবার অর্জনের আগে অন্য থ্রেড শর্ত খেতে পারা Win32-এর মতোই, আর ডকুমেন্টেশনও ধরে নেয় “জাগা থ্রেড তাকে ওয়েটে ঢোকানো শর্ত আবার মূল্যায়ন করবে, আর দরকার হলে আবার Wait ডাকবে”।610
flowchart TB
accTitle: প্রতিটি স্তরে প্রেডিকেট আবার যাচাই দরকার
accDescr: C++ std::condition_variable, .NET Monitor, Win32 CONDITION_VARIABLE ও নিম্ন-স্তরের WaitOnAddress — প্রতিটি স্তরে অফিসিয়াল ডকুমেন্টেশন জাগরণের পর শর্ত আবার যাচাই চায়
cpp["C++ std::condition_variable"] --> rule["জাগরণে শর্ত আবার যাচাই (while)"]
net[".NET Monitor.Wait"] --> rule
win["Win32 CONDITION_VARIABLE"] --> rule
woa["WaitOnAddress"] --> rule
চিত্র ৩: ভাষা বা ফ্রেমওয়ার্ক বদলালেও প্রতিটি ওয়েট-প্রিমিটিভ স্তরে অফিসিয়াল শর্ত একই: জাগার পর আবার যাচাই।
৫. সঠিকভাবে অপেক্ষা — while ও প্রেডিকেট দিয়ে লিখুন
এখান থেকে বাস্তবায়ন। নীতি মাত্র তিনটি।
- যার অপেক্ষা করেন তা স্টেট (প্রেডিকেট) হিসেবে ধরুন, “নোটিফিকেশন” হিসেবে নয়। শর্ত লক-রক্ষিত শেয়ার্ড স্টেট — “কিউ খালি নয়?”, “ফ্ল্যাগ সেট?” — “আমি কি জেগেছি?” নয়।
waitসবসময় শর্তের while লুপের ভেতরে রাখুন। প্রতি জাগরণে শর্ত যাচাই করুন, আর না ধরলে আবার ঘুমান।- একই লকের নিচে শর্ত আপডেট ও যাচাই করুন। নোটিফায়ার স্টেট আপডেট করে তারপর নোটিফাই করে।
flowchart TB
accTitle: সঠিক ওয়েট লুপের প্রবাহ
accDescr: লক নিয়ে শর্ত যাচাই করুন; না ধরলে লক ছেড়ে ঘুমান; জাগরণে লক আবার অর্জন করে শর্ত যাচাইয়ে ফিরুন। শর্ত ধরলেই লক ধরে এগোান
l["লক অর্জন"] --> c{"শর্ত পূর্ণ?"}
c -->|"না"| s["wait (লক ছেড়ে ঘুম)"]
s --> wk["জাগরণ (লক আবার অর্জন)"]
wk --> c
c -->|"হ্যাঁ"| go["লক ধরেই এগোান"]
চিত্র ৪: সঠিক ওয়েট একটি লুপ, আর শর্ত যাচাই ও প্রক্রিয়ার মাঝে ফাঁক নেই (দুটোই লক ধরে হয়)।
এই আকৃতির সহজে-মিস হওয়া সুবিধা আছে। while লুপ ছাড়ার মুহূর্তে লক এখনও ধরে “শর্ত ধরে” প্রতিষ্ঠিত। Spurious wakeup-এর বিরুদ্ধে যে লুপ রক্ষা করে, সেটাই শর্ত যাচাই ও প্রক্রিয়ার মাঝে রেস-কন্ডিশন ফাঁক নেই তার নিশ্চয়তা।
Win32 (C)-এ মৌলিক আকৃতি
CRITICAL_SECTION cs;
CONDITION_VARIABLE cv;
int queueCount = 0; // shared state protected by cs
// Initialise once at startup (for static initialisation, cv = CONDITION_VARIABLE_INIT)
InitializeCriticalSection(&cs);
InitializeConditionVariable(&cv);
// Waiter (consumer)
EnterCriticalSection(&cs);
while (queueCount == 0) { // always while, never if
SleepConditionVariableCS(&cv, &cs, INFINITE);
}
// Here the lock is held and queueCount > 0 is guaranteed
--queueCount;
LeaveCriticalSection(&cs);
// Notifier (producer)
EnterCriticalSection(&cs);
++queueCount; // update the state under the lock
LeaveCriticalSection(&cs);
WakeConditionVariable(&cv); // notifying after releasing the lock is fine
নোটিফিকেশন (WakeConditionVariable) লকের ভেতর থেকে বা বাইরে থেকে ডাকা যায়, কিন্তু ডকুমেন্টেশন বলে লক ছাড়ার পর জাগানো সাধারণত ভালো, কনটেক্সট সুইচ কমাতে।1 অন্যদিকে স্টেট আপডেট নিজে (++queueCount) সবসময় লকের নিচেই হতে হয়। দুটো গুলিয়ে ফেলবেন না।
C++-এ মৌলিক আকৃতি — প্রেডিকেট wait ডিফল্ট করুন
std::mutex m;
std::condition_variable cv;
std::queue<Item> q;
// Waiter
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&] { return !q.empty(); }); // internally while(!pred) wait
Item item = std::move(q.front());
q.pop();
lk.unlock();
// Notifier
{
std::lock_guard<std::mutex> lk(m);
q.push(std::move(item));
}
cv.notify_one();
প্রেডিকেট-আকারের wait লুপ আপনার হয়ে চালায়, তাই হাতে-লেখা while অপ্রয়োজনীয়। হাতে-লেখা লুপ এখনও আছে এমন বিদ্যমান কোড ঠিক করলে while (q.empty()) cv.wait(lk); সঠিক আকৃতি, তাই তাড়াতাড়ি আবার লেখার দরকার নেই। একমাত্র ভুল আকৃতি if (q.empty()) cv.wait(lk);।
C#-এ মৌলিক আকৃতি
private readonly object _gate = new();
private readonly Queue<Item> _queue = new();
// Waiter
lock (_gate)
{
while (_queue.Count == 0) // always while, never if
{
Monitor.Wait(_gate);
}
var item = _queue.Dequeue();
}
// Notifier
lock (_gate)
{
_queue.Enqueue(item);
Monitor.Pulse(_gate); // Monitor.Pulse can only be called inside the lock
}
Monitor.Wait / Pulse / PulseAll শুধু লকের ভেতর থেকে (lock ব্লক) ডাকা যায়, যা Win32 থেকে আলাদা। লকের বাইরে ডাকলে SynchronizationLockException থ্রো করে।10
টাইমআউটসহ অপেক্ষা — ডেডলাইন থেকে বাকি সময় হিসাব করুন
টাইমআউটসহ অপেক্ষা করলে প্রতি লুপ ইটারেশনে “একই টাইমআউট মান” পাঠালে spurious wakeup হলেই ওয়েট লম্বা হয়। সঠিক আকৃতি আগে ডেডলাইন স্থির করে বাকি সময় আবার হিসাব করা।
ULONGLONG deadline = GetTickCount64() + timeoutMs;
EnterCriticalSection(&cs);
while (queueCount == 0) {
ULONGLONG now = GetTickCount64();
if (now >= deadline) {
break; // timeout (condition still unsatisfied)
}
if (!SleepConditionVariableCS(&cv, &cs, (DWORD)(deadline - now)) &&
GetLastError() != ERROR_TIMEOUT) {
break; // on failure other than timeout, stop waiting and leave
}
// Confirm ERROR_TIMEOUT finally via the while condition and the deadline check
}
BOOL ready = (queueCount > 0);
if (ready) { --queueCount; }
LeaveCriticalSection(&cs);
flowchart TB
accTitle: টাইমআউটসহ ওয়েটের সঠিক প্রবাহ
accDescr: আগে ডেডলাইন স্থির করুন, আর প্রতি জাগরণে শর্ত ও ডেডলাইন যাচাই করুন; এখনও সময় থাকলে বাকি সময় আবার হিসাব করে ওয়েটে ফিরুন
d["ডেডলাইন স্থির"] --> c{"শর্ত পূর্ণ?"}
c -->|"হ্যাঁ"| go["প্রক্রিয়ায় এগোান"]
c -->|"না"| t{"ডেডলাইন পার?"}
t -->|"হ্যাঁ"| to["টাইমআউট সামলান"]
t -->|"না"| w["বাকি সময় হিসাব করে অপেক্ষা"]
w --> c
চিত্র ৫: টাইমআউটসহ ওয়েট “একই ওয়েট স্থায়িত্ব” আবার পাঠায় না; ডেডলাইন থেকে বাকি সময় আবার হিসাব করে।
C++-এ এই হিসাব, ডেডলাইনসহ, wait_until (পরম সময়) প্লাস প্রেডিকেট ওভারলোডে ছেড়ে দেওয়া যায়। টাইমআউটে ফিরলেও প্রেডিকেটের শেষ মান দেয়, তাই “টাইমআউট হয়েছি, নাকি পেরেছি?” প্রেডিকেটেও ঠিক করা যায়।5
৬. এড়ানোর আকৃতির ক্যাটালগ
শুধু if দিয়ে একবার যাচাই। এটি নিবন্ধের নায়ক। Spurious wakeup বা stolen wakeup হওয়ার মুহূর্তে শর্ত অপূর্ণ রেখে প্রক্রিয়া এগোয়। খালি কিউ থেকে নেওয়া, ইনিশিয়ালাইজ না হওয়া ডেটা ছোঁয়া, ডাবল ফ্রি — উপসর্গ হয়ে যায় “শুধু মাঝে মাঝে দেখা ক্র্যাশ বা ডেটা দূষণ”।
লকের বাইরে শর্ত যাচাই বা আপডেট। ওয়েটার লকের বাইরে শর্ত দেখে “এখনও না” ঠিক করে, আর wait-এ ঢোকার ফাঁকে নোটিফায়ার স্টেট আপডেট করে নোটিফিকেশন পাঠালে নোটিফিকেশন ওয়েটারহীন কন্ডিশন ভেরিয়েবলে ফায়ার হয়ে মিলিয়ে যায়। ওয়েটার তারপর wait-এ ঢোকে আর আর কখনো আসবে না এমন নোটিফিকেশনের অপেক্ষা করে। সেটা lost wakeup, spurious wakeup-এর আয়না। কন্ডিশন ভেরিয়েবলের ওয়েট API “পরমাণুভাবে লক ছেড়ে ঘুমানো” ডিজাইন ঠিক এই ফাঁক বন্ধ করতে।1 লক শৃঙ্খলা রাখলে হয় না।
sequenceDiagram
accTitle: Lost wakeup-এর টাইমলাইন
accDescr: ওয়েটার লকের বাইরে শর্ত যাচাই করলে ও নোটিফায়ার wait-এ ঢোকার ফাঁকে স্টেট আপডেট করে নোটিফাই করলে নোটিফিকেশন ওয়েটারহীন কন্ডিশন ভেরিয়েবলে পাঠানো হয়ে মিলিয়ে যায়, আর ওয়েটার আর কখনো আসবে না এমন নোটিফিকেশনের অপেক্ষা করে
participant W as ওয়েটার
participant N as নোটিফায়ার
W->>W: লকের বাইরে শর্ত যাচাই (অপূর্ণ)
N->>N: স্টেট আপডেট ও নোটিফাই
Note over N: এই মুহূর্তে ওয়েটার নেই
W->>W: wait-এ ঢোকা
Note over W: নোটিফিকেশন ইতিমধ্যে চলে গেছে, আর জাগে না
চিত্র ৬: লকের বাইরে শর্ত যাচাই করলে নোটিফিকেশন যাচাই ও wait-এর ফাঁক দিয়ে পিছলে যায় — “lost wakeup”।
কন্ডিশন ভেরিয়েবলের “ক্ষণস্থায়ী নোটিফিকেশন” ইভেন্টের পালস দিয়ে নকল করা। ইভেন্ট নিজে (CreateEvent + SetEvent) অ্যান্টি-প্যাটার্ন নয়। একজন কনজিউমার খালি না হওয়া পর্যন্ত কিউ প্রক্রিয়া করে এমন সেটআপে জাগানো সিগন্যাল, অথবা একবার তোলা আর নামানো হয় না এমন স্টপ নির্দেশ (ম্যানুয়াল-রিসেট ইভেন্ট), ইভেন্টের সঠিক ব্যবহার; আর WaitForMultipleObjects দিয়ে অন্য ওয়েট টার্গেটের সাথে জোড়াতে চাইলে, অথবা প্রক্রিয়া সীমানা পার করতে চাইলে, কন্ডিশন ভেরিয়েবল — প্রক্রিয়া পার করে শেয়ার করা যায় না এমন ইউজার-মোড অবজেক্ট — সেটাই ব্যবহার করা যায় না।1 যা বিপজ্জনক তা ইভেন্ট অপারেশন দিয়ে কন্ডিশন ভেরিয়েবলের ক্ষণস্থায়ী নোটিফিকেশন নকল করার চেষ্টা যা “ঠিক সেই মুহূর্তে অপেক্ষায় থাকা থ্রেডকেই জাগায় আর স্টেট রেখে যায় না”। সেই ধারণা প্রায় সবসময় পরের আইটেমে নিয়ে যায়, PulseEvent।
PulseEvent ব্যবহার। এটি ম্যানুয়াল-রিসেট ইভেন্টে “এখন অপেক্ষায় থাকা সবাইকে জাগিয়ে ইভেন্ট সঙ্গে সঙ্গে নন-সিগন্যাল অবস্থায় ফেরায়” এমন API, কিন্তু Microsoft নিজে ডকুমেন্টেশনে বলে “এই ফাংশন অনির্ভরযোগ্য এবং ব্যবহার করা উচিত নয়। মূলত পেছনের সামঞ্জস্যের জন্য আছে। বদলে কন্ডিশন ভেরিয়েবল ব্যবহার করুন।” কারণ অপেক্ষায় থাকা থ্রেড কার্নেল-মোড APC দিয়ে সাময়িকভাবে ওয়েট অবস্থা থেকে সরানো যেতে পারে আর APC শেষে ওয়েটে ফিরতে পারে। সেই সংক্ষিপ্ত ফাঁকে PulseEvent ডাকা হলে সেই থ্রেড “ডাকার মুহূর্তে যারা অপেক্ষা করছিল” তাদের মধ্যে থাকে না এবং জাগে না।7 কার্নেল APC OS অভ্যন্তরে ব্যবহার করে; অ্যাপ নিয়ন্ত্রণ করতে পারে না।11 এই সমস্যা স্ট্যাটিক-অ্যানালিসিস সতর্কতাও (C28648)।12 Spurious wakeup যদি “অতিরিক্ত জাগার” সমস্যা হয়, এটি “জাগা উচিত ছিল তবু ঘুমিয়ে থাকার” সমস্যা, আর while লুপ বাঁচাতে পারে না — কারণ নোটিফিকেশন নিজেই হারিয়েছে।
স্টেট আপডেটের আগে, লক না ধরে, শুধু নোটিফিকেশন আগে পাঠানো। স্টেট এখনও পুরোনো থাকতে WakeConditionVariable ডাকা, আর তারপরই লক নিয়ে স্টেট আপডেট — সেই ক্রমে জাগা থ্রেড যাচাইয়ের সময় শর্ত এখনও অপূর্ণ দেখে আবার ঘুমায়। আর নোটিফিকেশন না এলে সেখানেই থাকে। লক্ষ করুন, একই লক ধরে “নোটিফাই → আপডেট → ছাড়া” লিখলে আসল ক্ষতি নেই, কারণ ওয়েটার লক আবার অর্জন না করা পর্যন্ত শর্ত যাচাই করতে পারে না। তবু পাঠককে এই নিরাপত্তা শর্ত প্রতিবার যাচাই করতে না দিয়ে “লকের নিচে স্টেট আপডেট, তার পর নোটিফাই” ক্রম মানক করা নিরাপদ।
৭. ধরলে কীভাবে অনুসন্ধান করবেন
Spurious wakeup জড়িত বাগের বৈশিষ্ট্য “শুধু বিরলে দেখা”। উপসর্গ থেকে পেছনে হাঁটলে নিচের দুই পরিবারে ভাগে।
পরিবার ১: শর্ত অপূর্ণ রেখে প্রক্রিয়া এগোয়। খালি কিউ থেকে নেওয়া এক্সেপশন বা ক্র্যাশ, ফল মিস ইত্যাদি। প্রেডিকেটহীন ওয়েট সন্দেহ করুন। কোড রিভিউতে যান্ত্রিকভাবে আঁচড়ানো যায় — cv.wait(-এর শুধু একটি আর্গুমেন্ট আছে এমন জায়গা, আর SleepConditionVariableCS / Monitor.Wait while নয় if-এ মুড়ানো জায়গা খুঁজুন। এই যাচাইয়ের জন্য পুনরুৎপাদনের অপেক্ষা লাগে না, আর সবচেয়ে বেশি লিভারেজের পদক্ষেপ।
পরিবার ২: জাগা উচিত এমন থ্রেড জাগে না (হ্যাং)। Lost wakeup (লকের বাইরে শর্ত যাচাই, অথবা স্টেট আপডেটের আগে লকের বাইরে নোটিফাই) ও PulseEvent সন্দেহ করুন। হ্যাং প্রক্রিয়া থেকে ডাম্প নিয়ে প্রতিটি থ্রেডের স্ট্যাক দেখুন, আর কোন থ্রেড কোন ওয়েট API-এ আটকে আছে চিহ্নিত করা যায়। সেখান থেকে কোডে তাড়ান “সেই নোটিফিকেশন কে পাঠাবে ছিল, আর কোন ক্রমে”।
flowchart TB
accTitle: উপসর্গ থেকে ট্রায়াজ প্রবাহ
accDescr: শর্ত অপূর্ণ রেখে প্রক্রিয়া এগোলে কোড খুঁজে প্রেডিকেটহীন ওয়েট আঁচান; থ্রেড না জাগলে ডাম্প থেকে ওয়েট সাইট চিহ্নিত করে lost wakeup বা PulseEvent সন্দেহ করুন
s["শুধু বিরলে দেখা বাগ"] --> a["শর্ত অপূর্ণ রেখে প্রক্রিয়া এগোয়"]
s --> b["জাগা উচিত এমন থ্রেড জাগে না"]
a --> a1["প্রেডিকেটহীন ওয়েটের জন্য কোড খুঁজুন"]
b --> b1["ডাম্প থেকে অপেক্ষায় থাকা থ্রেড চিহ্নিত"]
a1 -.-> a2["if থেকে while, অথবা প্রেডিকেট ওয়েট"]
b1 -.-> b2["Lost wakeup বা PulseEvent সন্দেহ"]
চিত্র ৭: উপসর্গ “অতিরিক্ত এগোানো” নাকি “কখনোই না জাগা” দুটোই কী সন্দেহ করবেন ও কীভাবে অনুসন্ধান করবেন ভাগ করে।
পুনরুৎপাদন করতে চাইলে মানক পদক্ষেপ রেস উইন্ডো চওড়া করা। ফিজিক্যাল কোরের চেয়ে বেশি থ্রেড ব্যবহার করে, ওয়েট ও নোটিফাইয়ের মাঝে ইচ্ছাকৃত Sleep ঢুকিয়ে, আর ডিবাগ ও রিলিজ দুই বিল্ড চালিয়ে টাইমিং জিটার বাড়ান। “প্রেডিকেটহীন ওয়েট ঠিক করার পর পুনরুৎপাদন থেমেছে” নিশ্চিত করলে একই স্ট্রেসের নিচে তুলনা করুন।
৮. সারসংক্ষেপ — একটি চেকলিস্ট
waitথেকে ফেরার পথ তিনটি — প্রকৃত নোটিফিকেশন, spurious wakeup ও stolen wakeup — আর কলার আলাদা করতে পারে না। তাই ওয়েট সবসময় শর্তের while লুপ হিসেবে লিখুন।- Spurious wakeup Win32, C++ ও POSIX ইচ্ছাকৃতভাবে পারফরম্যান্সের ট্রেড-অফ হিসেবে অনুমোদন করা আচরণ, আর OS ফিক্স বা লাইব্রেরি বদলে যাবে না। .NET-এর
Monitor.Waitকারণ ছাড়া জাগবে ধরে নেওয়া হয় না, কিন্তু stolen wakeup ও টাইমআউট থাকায় একই while শৃঙ্খলা এখনও দরকার। - C++-এ প্রেডিকেট আকার
wait(lock, pred)ডিফল্ট করুন। লাইব্রেরি লুপ চালায়। - একই লকের নিচে শর্ত আপডেট ও যাচাই করুন। নোটিফিকেশন “স্টেট আপডেটের পর” পাঠান। Win32/C++ নোটিফিকেশন লক ছাড়ার পর হতে পারে; C#-এর
Pulseশুধু লকের ভেতরে। - টাইমআউটসহ ওয়েটে ডেডলাইন স্থির করে বাকি সময় আবার হিসাব করুন। C++-এ
wait_untilপ্লাস প্রেডিকেট। - কন্ডিশন ভেরিয়েবলের ক্ষণস্থায়ী নোটিফিকেশন ইভেন্টের পালস দিয়ে নকল করবেন না। বিশেষ করে
PulseEventঅফিসিয়াল ডকুমেন্টেশন স্পষ্ট বলে “ব্যবহার করবেন না, বদলে কন্ডিশন ভেরিয়েবল ব্যবহার করুন”। ইভেন্ট নিজে স্টপ নির্দেশ,WaitForMultipleObjects-এর সাথে জোড়া ও প্রক্রিয়া-পার সিঙ্ক্রোনাইজেশনের সঠিক হাতিয়ারই থাকে। - রিভিউতে যান্ত্রিকভাবে খুঁজুন “প্রেডিকেটহীন ওয়েট” ও “
if+ wait”। পুনরুৎপাদনের অপেক্ষা না করে বিরলে-দেখা বাগ মারা যায়।
Spurious wakeup, নামের অদ্ভুততার বিপরীতে, সমাধানের এক-লাইনের কীওয়ার্ডে সিদ্ধ হয় — ifকে while করুন। আর সেই এক লাইনের পেছনে কন্ডিশন-ভেরিয়েবল হাতিয়ারের ডিজাইন ধারণা: “সঠিক নোটিফিকেশন ব্যয়বহুল, তাই যাচাই ওয়েটারের দায়িত্ব”। যন্ত্র হিসেবে বুঝলে ভাষা বা ফ্রেমওয়ার্ক বদলালেও একই শৃঙ্খলা দ্বিধাহীনভাবে প্রয়োগ করতে পারা উচিত।
সম্পর্কিত নিবন্ধ
- ব্যবহারিক মাল্টিথ্রেডিং সেরা অনুশীলন: C++ সংস্করণ — RAII ও jthread দিয়ে কাঠামোতে দুর্ঘটনা মুছে ফেলা
- ব্যবহারিক মাল্টিথ্রেডিং সেরা অনুশীলন: C সংস্করণ — Win32 API পথে নিরাপদে লেখা
- ব্যবহারিক মাল্টিথ্রেডিং সেরা অনুশীলন: .NET সংস্করণ — আরও থ্রেড যোগ করার আগে কী ঠিক করবেন
- Windows-এ Sleep(1)-এর বদলে ইভেন্ট ওয়েট কেন পছন্দ করা উচিত
- শেয়ার্ড মেমরির ফাঁদ ও ব্যবহারিক সেরা অনুশীলন
- Windows I/O-এর গভীরতা (পর্ব ২) — সিঙ্ক্রোনাস ও অ্যাসিঙ্ক্রোনাস I/O: OVERLAPPED আসলে কী
সম্পর্কিত পরামর্শ ক্ষেত্র
KomuraSoft LLC মাল্টিথ্রেড ডিজাইন রিভিউ, “শুধু মাঝে মাঝে পুনরুৎপাদন হয়” এমন ক্র্যাশ ও হ্যাঙের মূল কারণ অনুসন্ধান (ডাম্প বিশ্লেষণ), আর লেগাসি সিঙ্ক্রোনাইজেশন কোড (ইভেন্ট- ও PulseEvent-নির্ভর ইত্যাদি) কন্ডিশন-ভেরিয়েবল ভিত্তিতে স্থানান্তর সামলায়। উপসর্গ ট্রায়াজ থেকে শুরু করলেই চলে — নির্দ্বিধায় যোগাযোগ করুন।
- টেকনিক্যাল কনসালটিং ও ডিজাইন রিভিউ
- বাগ অনুসন্ধান ও মূল কারণ বিশ্লেষণ
- Windows অ্যাপ্লিকেশন ডেভেলপমেন্ট
- যোগাযোগ করুন
তথ্যসূত্র
-
Microsoft Learn, Condition Variables। কন্ডিশন ভেরিয়েবল ইউজার-মোড অবজেক্ট যা পরমাণুভাবে লক ছেড়ে ওয়েটে ঢোকে; spurious wakeup (স্পষ্ট জাগানোর সাথে বাঁধা নয়) ও stolen wakeup (জাগা থ্রেডের আগে অন্য থ্রেড চলা) থাকে, তাই ওয়েট থেকে ফেরার পর while লুপে প্রেডিকেট আবার যাচাই করা উচিত; এবং নোটিফিকেশন লকের ভেতর বা বাইরে সম্ভব, কিন্তু লক ছাড়ার পর জাগানো কনটেক্সট সুইচ কমাতে ভালো সম্পর্কে। ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, SleepConditionVariableCS function (synchapi.h)। নির্দিষ্ট ক্রিটিক্যাল সেকশন পরমাণুভাবে ছেড়ে কন্ডিশন ভেরিয়েবলে অপেক্ষা; জাগা থ্রেড ফেরার আগে ক্রিটিক্যাল সেকশন আবার অর্জন; টাইমআউটে ERROR_TIMEOUT ফেরা; এবং spurious wakeup ও stolen wakeup থাকে, তাই ওয়েট থেকে ফেরার পর প্রেডিকেট আবার যাচাই করা উচিত (সাধারণত while লুপে) সম্পর্কে। ↩ ↩2
-
The Open Group Base Specifications, pthread_cond_timedwait, pthread_cond_wait। pthread_cond_wait / pthread_cond_timedwait থেকে spurious wakeup ঘটতে পারে; wait থেকে ফেরা প্রেডিকেটের মান নিয়ে কিছু বলে না, তাই প্রেডিকেট আবার মূল্যায়ন করা উচিত; এবং Rationale বলে “ঠিক একটি জাগাও” বাস্তবায়ন বিশেষ করে মাল্টিপ্রসেসরে কন্ডিশন-ভেরিয়েবল অপারেশন ধীর করতে পারে, আর spurious wakeup অনুমোদন প্রেডিকেট-যাচাই লুপ বাধ্য করে অ্যাপ আরও মজবুত করে সম্পর্কে। ↩ ↩2 ↩3
-
cppreference.com, std::condition_variable::wait। প্রেডিকেটহীন wait spurious wakeup-এ আনব্লক হতে পারে; এবং প্রেডিকেট ওভারলোড while (!pred()) wait(lock);-এর সমতুল্য ও প্রতি নোটিফিকেশন বা spurious wakeup-এ লক আবার অর্জন করে প্রেডিকেট যাচাই করা লুপ হিসেবে সংজ্ঞায়িত সম্পর্কে। ↩ ↩2
-
Microsoft Learn, condition_variable Class। প্রেডিকেটহীন wait notify_one / notify_all-এ আনব্লক হয় এবং spuriousভাবেও জাগতে পারে বলা; প্রেডিকেট আকার wait(lock, pred) কার্যত while (!Pred()) wait(Lck); চালায়; এবং wait_for / wait_until-এর একই বৈশিষ্ট্য ও প্রেডিকেট ওভারলোড সম্পর্কে। ↩ ↩2 ↩3
-
Microsoft Learn, Monitor.Wait Method। Wait লক ছেড়ে ওয়েটিং কিউতে ঢোকে; Pulse / PulseAll-এ জাগার পর লক আবার অর্জন না হওয়া পর্যন্ত ফেরে না; এবং উদ্দিষ্ট ব্যবহার জাগা থ্রেড তাকে ওয়েটে ঢোকানো শর্ত আবার মূল্যায়ন করে দরকার হলে আবার Wait ডাকা সম্পর্কে। ↩ ↩2
-
Microsoft Learn, PulseEvent function (winbase.h)। অপেক্ষায় থাকা থ্রেড কার্নেল-মোড APC দিয়ে সাময়িকভাবে ওয়েট অবস্থা থেকে সরানো যেতে পারে ও APC শেষে ফিরতে পারে, তাই সেই ফাঁকে PulseEvent ডাকলে থ্রেড রিলিজ হয় না; এবং PulseEvent তাই অনির্ভরযোগ্য ও নতুন অ্যাপে ব্যবহার করা উচিত নয়, বদলে কন্ডিশন ভেরিয়েবল ব্যবহার সম্পর্কে। ↩ ↩2
-
Microsoft Learn, Using Condition Variables। একটি ক্রিটিক্যাল সেকশন ও দুটি কন্ডিশন ভেরিয়েবল (BufferNotEmpty ও BufferNotFull) দিয়ে প্রডিউসার–কনজিউমার কিউ বাস্তবায়ন করা অফিসিয়াল নমুনা সম্পর্কে। ওয়েট প্রেডিকেট যাচাই করা লুপের ভেতরে করা হয়। ↩
-
Microsoft Learn, WaitOnAddress function (synchapi.h)। অ্যাড্রেসের মান বদলের অপেক্ষা করা ফাংশন সিগন্যাল হলে ফেরা নিশ্চিত কিন্তু অন্য কারণেও ফেরা অনুমোদিত; তাড়াতাড়ি জাগার উদাহরণ কম-মেমরি অবস্থা, একই অ্যাড্রেসের আগের জাগরণ পরিত্যাগ ও চেকড বিল্ড চালানো; এবং তাই ফেরার পর মান আবার তুলনা দরকার, অফিসিয়াল নমুনা নিজেই while লুপ সম্পর্কে। ↩
-
Microsoft Learn, Monitor.PulseAll Method। PulseAll থ্রেডকে ওয়েটিং কিউ থেকে রেডি কিউতে সরায়, আর লক ছাড়লে রেডি কিউয়ের পরের থ্রেড লক অর্জন করে; এবং Pulse / PulseAll / Wait শুধু সিঙ্ক্রোনাইজেশন ব্লকের ভেতর থেকে ডাকা যায় সম্পর্কে। ↩ ↩2
-
Microsoft Learn, Waits and APCs। কার্নেল APC প্রিএম্পটিভভাবে চলে, আর সিস্টেম অভ্যন্তরে ওয়েট API থেকে না ফিরে ওয়েট বাধা দিয়ে আবার শুরু করে, তাই KePulseEvent-এর মতো ক্ষণস্থায়ী সিগন্যাল সেই ফাঁকে মিস হতে পারে সম্পর্কে। ↩
-
Microsoft Learn, C28648: PulseEvent is an unreliable function। PulseEvent ব্যবহারের স্ট্যাটিক অ্যানালিসিস সতর্কতা; APC-এর কারণে ওয়েটের বাইরে থাকা থ্রেড রিলিজ না হয়ে চিরকাল হ্যাং করতে পারা; এবং SetEvent বা অন্য সিঙ্ক্রোনাইজেশন অবজেক্ট দিয়ে বদলানোর নির্দেশনা সম্পর্কে। ↩
সম্পর্কিত নিবন্ধ
কাছাকাছি বিষয়ে গভীরে যেতে একই ট্যাগযুক্ত সাম্প্রতিক নিবন্ধ।
DllMain ও লোডার লক — DLL ইনিশিয়ালাইজেশনে "কিছুই করবেন না" বলা হয় তার আসল কারণ
DllMain থেকে LoadLibrary ডাকা বা অন্য থ্রেডের সাথে সিঙ্ক্রোনাইজ করা যায় না কেন। প্রাথমিক উৎস থেকে এই নিবন্ধ ব্যাখ্যা করে লোডার লক প্রতিট...
Win32 থ্রেড পুল API — CreateThreadpoolWork দিয়ে থ্রেড তৈরি না করে কনকারেন্সি
নেটিভ কোডে চারদিকে CreateThread ডাকছেন? এই নিবন্ধ Vista-তে নতুন করে সাজানো Win32 থ্রেড পুল API ব্যাখ্যা করে — work, timer, wait ও io চারট...
নেমড পাইপ ব্যবহারিকভাবে — ডিজাইন থেকে নিরাপত্তা পর্যন্ত Windows-এর মানক IPC
Windows-এর মানক আন্তঃপ্রক্রিয়া যোগাযোগ নেমড পাইপের ব্যবহারিক গাইড। প্রাথমিক উৎস থেকে এই নিবন্ধ বাইট ও মেসেজ মোডের পছন্দ, একাধিক ক্লায়েন...
"Not Responding" আসলে কী — Windows কীভাবে অ্যাপ হ্যাং ধরে, আর যে ডিজাইন হ্যাং করে না
Windows-এর "Not Responding" এমন যন্ত্র যেখানে OS বিচার করে উইন্ডো ৫ সেকেন্ড মেসেজ তোলেনি আর তাকে ঘোস্ট উইন্ডো দিয়ে বদলায়। এই নিবন্ধ সেই...
ঘুম থেকে জাগলে ভাঙে যে অ্যাপ — Windows পাওয়ার ইভেন্টের কাজ ও যে ব্যবসায়িক অ্যাপ তা সহ্য করে
ল্যাপটপ খুললেন আর ব্যবসায়িক অ্যাপের সংযোগ মৃত — কারণ ঘুম ধরে না নেওয়া ডিজাইন। এই নিবন্ধ WM_POWERBROADCAST নোটিফিকেশন প্রবাহ, Modern Sta...
সম্পর্কিত বিষয়
এই পৃষ্ঠাগুলো বিষয়টিকে সেবা ও সিদ্ধান্তের বৃহত্তর প্রেক্ষাপটে স্থাপন করে।
Windows-এর প্রযুক্তিগত বিষয়
Windows ডেভেলপমেন্ট, বাগ তদন্ত ও বিদ্যমান সম্পদ ব্যবহারের প্রবেশদ্বার।
এই বিষয়ের সাথে সম্পর্কিত সেবা
নিবন্ধটি নিচের সেবাগুলোর সাথে সরাসরি সম্পর্কিত।
Windows অ্যাপ ডেভেলপমেন্ট
ব্যবসায়িক অ্যাপ, ডিভাইস ইন্টিগ্রেশন ও যোগাযোগ টুল, চাহিদা থেকে ডেভেলপমেন্ট পর্যন্ত।
প্রায়শ জিজ্ঞাসিত প্রশ্ন
এই নিবন্ধের বিষয়ে পরামর্শে প্রায়ই আসা প্রশ্ন।
- Spurious wakeup কি OS বা লাইব্রেরির বাগ?
- না — এটি স্পেসিফিকেশনে লেখা আচরণ। Win32-এর SleepConditionVariableCS, C++-এর std::condition_variable ও POSIX-এর pthread_cond_wait সবগুলোর অফিসিয়াল ডকুমেন্টেশন বা স্ট্যান্ডার্ড স্পষ্ট বলে নোটিফিকেশনের সাথে বাঁধা নয় এমন জাগরণ ঘটতে পারে। নিষেধ করা বাস্তবায়ন তাত্ত্বিকভাবে সম্ভব, কিন্তু প্রতিটি কন্ডিশন-ভেরিয়েবল অপারেশন (বিশেষ করে মাল্টিপ্রসেসরে নোটিফিকেশন) ধীর করত, তাই ট্রেড-অফ হলো "ওয়েটার শর্ত আবার যাচাই করলে সঠিকতা থাকে" বুঝে অনুমোদন। তাই প্রতিকার OS ফিক্সের অপেক্ষা নয়, সবসময় while লুপের ভেতরে ওয়েট লেখা (অথবা প্রেডিকেট-আকারের ওয়েট ব্যবহার)।
- ওয়েটকে while লুপে মুড়ালে পারফরম্যান্স ক্ষতি হয়?
- বাস্তবে খরচ নগণ্য। while লুপ যা যোগ করে তা প্রতি জাগরণে একটি অতিরিক্ত শর্ত যাচাই, আর সেটা লক ইতিমধ্যে ধরে সস্তা তুলনা। Spurious wakeup নিজেই বিরল, তাই অতিরিক্ত লুপ ইটারেশন শুধু ব্যতিক্রমী ক্ষেত্রে হয়। অন্যদিকে যাচাইকে if রাখার খরচ "শর্ত অপূর্ণ রেখে প্রক্রিয়া এগোয়" এমন "শুধু বিরলে পুনরুৎপাদন হয় এমন বাগ" — তুলনা চলে না। কন্ডিশন-ভেরিয়েবল ওয়েট খরচে যা সত্যি প্রাধান্য পায় তা লক কনটেনশন ও কতবার নোটিফাই করেন, while আছে কি না নয়।
- C++-এর প্রেডিকেট-আকারের ওয়েট ব্যবহার করলে spurious wakeup ভুলে যাওয়া যায়?
- ওয়েট লুপের জন্য হ্যাঁ: cv.wait(lock, pred) কার্যত while (!pred()) wait(lock); তাই spurious wakeup ও stolen wakeup দুটোই স্বয়ংক্রিয়ভাবে শোষিত হয়। নতুন C++ কোডে প্রেডিকেট ওভারলোডই ডিফল্ট হোক। প্রেডিকেট যে শেয়ার্ড স্টেট পড়ে তার আপডেট এখনও একই মিউটেক্স দিয়ে রক্ষা করতে হয়, আর নোটিফায়ারকে নোটিফাই ডাকার আগে সেই স্টেট আপডেট করতেই হয়। প্রেডিকেট ওয়েট লুপ আপনার হাত থেকে নেয়; লক শৃঙ্খলা নেয় না।
- C#-এর Monitor.Wait-এও একই সমস্যা হয়?
- হ্যাঁ। Monitor.Wait-এ অপেক্ষায় থাকা থ্রেড Pulse/PulseAll-এ জাগে তারপর Wait থেকে ফেরার আগে লক আবার অর্জন করে, কিন্তু সেই ফাঁকে অন্য থ্রেড আগে লক নিয়ে শর্ত খেয়ে ফেলতে পারে (stolen wakeup)। Microsoft-এর ডকুমেন্টেশন ধরে নেয় জাগা থ্রেড তাকে অপেক্ষা করানো শর্ত আবার মূল্যায়ন করবে, আর দরকার হলে আবার Wait ডাকবে। তাই C#-এও মৌলিক আকৃতি while (!condition) Monitor.Wait(gate);। Win32 থেকে আলাদা একটি সীমাবদ্ধতা: Wait/Pulse শুধু lock স্টেটমেন্টের ভেতর থেকে ডাকা যায়।
- WaitForSingleObject দিয়ে ইভেন্টে অপেক্ষা করলেও spurious wakeup হয়?
- সাধারণ (নন-অ্যালার্টেবল) ওয়েটে WAIT_OBJECT_0 শুধু অবজেক্ট সত্যি সিগন্যাল হলে ফেরে; কন্ডিশন ভেরিয়েবলের মতো "কারণহীন জাগরণ" নেই। তবু "ইভেন্ট সিগন্যাল হয়েছে" আর "আপনার অ্যাপের শর্ত ধরে" আলাদা জিনিস। একই ইভেন্টে কয়েকজন কনজিউমার জাগলে যে থ্রেড আগে লক নেয় সে শর্ত খায়, তাই জাগার পর শর্ত আবার যাচাই করতেই হয়। কন্ডিশন ভেরিয়েবলের "ঠিক সেই মুহূর্তে যে অপেক্ষা করছে শুধু তাকে জাগাও" ক্ষণস্থায়ী নোটিফিকেশন ইভেন্ট দিয়ে নকল করার ডিজাইন PulseEvent-এর নির্ভরযোগ্যতা সমস্যায়ও পড়ে, তাই প্রক্রিয়ার ভেতরে শর্তের অপেক্ষায় কন্ডিশন ভেরিয়েবলই নিরাপদ হাতিয়ার।