ব্যবহারিক মাল্টিথ্রেডিং সেরা অনুশীলন: C সংস্করণ — Win32 API পথে নিরাপদে লেখা
· Go Komura · Windows, মাল্টিথ্রেডিং, C, Win32 API, ব্যবসায়িক অ্যাপ, বাগ তদন্ত, ডিজাইন
«যন্ত্র নিয়ন্ত্রণের আবাসিক প্রক্রিয়া C-তে লিখছি।» «বিশ বছরের C অ্যাপে থ্রেড যোগ করতে হল।» «TerminateThread দিয়ে থ্রেড থামাই, কিন্তু মাঝে মাঝে পুরো প্রক্রিয়া আটকে যায়।» — C-তে মাল্টিথ্রেডিং সেই জগৎ যেখানে ভাষা সবচেয়ে কম সাহায্য দেয়। ব্যতিক্রম নেই, RAII নেই, টেমপ্লেট নেই; সিঙ্ক্রোনাইজেশনের সঠিকতা পুরোপুরি নির্ভর করে কোন API বেছে নেন আর কত শৃঙ্খলায় ডাকেন।
এই নিবন্ধ ব্যবহারিক মাল্টিথ্রেডিং সিরিজের C সংস্করণ। Win32 API-তে C লেখা ডেভেলপারদের লক্ষ্য করে এটি মাল্টিথ্রেড ডিজাইনের নীতি — থ্রেড ঢালাও উৎপাদন বন্ধ, শেয়ার করা পরিবর্তনযোগ্য অবস্থা কমানো, লক শৃঙ্খলা রাখা, আর শুরু করার আগে থামানোর নকশা — Win32-এর সরঞ্জামে ম্যাপ করে: থ্রেড কীভাবে তৈরি (_beginthreadex), সিঙ্ক্রোনাইজেশন অবজেক্ট কীভাবে বাছা, TerminateThread ছাড়া স্টপ পথ কীভাবে নকশা, আর DllMain-এর সীমা, আগস্ট ২০২৬ পর্যন্ত প্রাথমিক উৎসের চারপাশে সাজানো। একা পড়ার মতো লেখা। একই নীতি অন্য ভাষায় .NET সংস্করণ, C++ সংস্করণ ও Java সংস্করণ-এও আছে।
1. আগে উপসংহার
- থ্রেড
_beginthreadexদিয়ে তৈরি করুন,CreateThreadদিয়ে নয়। CRT ডাকা থ্রেড CreateThread দিয়ে তৈরি হলে কম মেমরিতে CRT প্রক্রিয়া শেষ করতে পারে।12 - প্রক্রিয়ার ভিতরে ডিফল্ট লক SRW লক; CRITICAL_SECTION কেবল পুনরাবৃত্ত অর্জন লাগলে। প্রক্রিয়ার ভিতরে বর্জনে Mutex ব্যবহার «সাধারণ ভুল» যা সবসময় কার্নেল মোড স্থানান্তর আনে।3
- একক ভেরিয়েবল Interlocked পরিবার দিয়ে আপডেট করুন।
volatileপরমাণুতাও গ্যারান্টি দেয় না, ক্রমও না। বেশিরভাগ Interlocked ফাংশন পূর্ণ মেমরি ব্যারিয়ার বহন করে।4 - অপেক্ষা কন্ডিশন ভেরিয়েবল (
SleepConditionVariableCSপরিবার) বা ইভেন্ট প্লাস অপেক্ষা ফাংশন দিয়ে।Sleep-ভিত্তিক পোলিং লুপ CPU ও সাড়া দুটোই নষ্ট করে।5 TerminateThreadকখনো ব্যবহার করবেন না। এটি বিপজ্জনক ফাংশন যা লক, হিপ ও DLL অবস্থা নষ্ট করে, আর কোড-বিশ্লেষণ সতর্কতা C6258-এর লক্ষ্য। থামানো সহযোগী শাটডাউন হিসেবে নকশা করুন: স্টপ ইভেন্ট প্লাসWaitForMultipleObjects।67- ছোট কাজ Windows থ্রেড পুল (
CreateThreadpoolWork)-এ দিন, নিজের থ্রেড দিয়ে সমান্তরাল করবেন না। পুলের থ্রেড কখনোExitThread/TerminateThreadদিয়ে শেষ করবেন না।89 - DllMain-এ থ্রেড তৈরি, সিঙ্ক্রোনাইজ বা থ্রেড শেষ হওয়ার অপেক্ষা করবেন না। লোডার লক ধরে ডাকা হয়, তাই ডেডলকের আঁতুড়ঘর।10
- C11-এর
<threads.h>VS 2022 17.8 থেকে ব্যবহারযোগ্য, কিন্তু<stdatomic.h>এখনও পরীক্ষামূলক। কেবল-Windows কোডবেসের জন্য Win32 পথ বাস্তব পছন্দ।11
2. মাল্টিথ্রেডিং কেন কঠিন? — রেস কন্ডিশন ও ডেডলক
মাল্টিথ্রেডিং যে সমস্যা আনে ভাষা নির্বিশেষে সেদ্ধ করলে দুই ধরন থাকে।
রেস কন্ডিশন এমন বাগ যেখানে ফল বদলায় কয়েকটি থ্রেড কোন ক্রমে কোন কোডখণ্ডে পৌঁছায় তার উপর। ক্লাসিক উদাহরণ শেয়ার করা কাউন্টার: একক রাশি count++ মেশিন-কোড স্তরে তিন ধাপে ভাঙে — পড়া, যোগ, ফিরিয়ে লেখা। দুই থ্রেড এই তিন ধাপে একসাথে ঢুকলে এক থ্রেডের যোগ অন্যের ফিরিয়ে লেখায় ওভাররাইট হয়ে হারায়।4 ফল রান থেকে রান বদলায়, কোন ফল পাবেন তা অনির্দেশ্য।
sequenceDiagram
participant A as থ্রেড A
participant M as শেয়ার করা ভেরিয়েবল count
participant B as থ্রেড B
Note over M: count = 10
A->>M: পড়া (10)
B->>M: পড়া (10)
A->>A: স্থানীয় যোগ (11)
B->>B: স্থানীয় যোগ (11)
A->>M: ফিরিয়ে লেখা (11)
B->>M: ফিরিয়ে লেখা (11)
Note over M: দুই বৃদ্ধি সত্ত্বেও count = 11<br/>থ্রেড A-এর বৃদ্ধি হারাল
চিত্র 1: ক্লাসিক রেস কন্ডিশন যেখানে শেয়ার করা কাউন্টার একটি বৃদ্ধি হারায়। count++-এর তিন ধাপের মধ্যে অন্য থ্রেড ঢুকলে যে ফিরিয়ে-লেখা পরে হয় সেটা অন্যটাকে ওভাররাইট করে
ডেডলক এমন অবস্থা যেখানে দুই থ্রেড প্রত্যেকে অন্যের ধরা লকের অপেক্ষা করে, কেউ এগোতে পারে না। থ্রেড A লক 1 ধরে লক 2-এর অপেক্ষা করে; থ্রেড B লক 2 ধরে লক 1-এর অপেক্ষা করে — এটুকুই যথেষ্ট দুজনে চিরকাল থেমে যেতে।
flowchart LR
A["থ্রেড A<br/>লক 1 ধরে"] -->|"লক 2 ছাড়ার অপেক্ষা"| B["থ্রেড B<br/>লক 2 ধরে"]
B -->|"লক 1 ছাড়ার অপেক্ষা"| A
চিত্র 2: ডেডলকের বৃত্তাকার অপেক্ষা। অপেক্ষার তীর যখন বলয় গঠন করে, সেই বলয়ের প্রতি থ্রেড চিরকাল থেমে যায়
দুটোই সময়নির্ভর: এক্সিকিউশন-ক্রম সংযোগ যা ডেভেলপমেন্ট মেশিনে হাজার হাজার রানে একবার দেখা যায়, ভিন্ন কোর সংখ্যা ও সময়ের গ্রাহক মেশিনে প্রতিদিন হতে পারে। «ডিবাগার লাগালে পুনরুৎপাদন হয় না» ঘটে কারণ পর্যবেক্ষণ নিজেই সময় বদলায় — রেস বাগের সাধারণ আচরণ। তাই এই নিবন্ধের প্রতি নীতি এক দিকে: সঠিক সিঙ্ক্রোনাইজ করার চিন্তার আগে সিঙ্ক্রোনাইজেশন লাগে এমন জায়গা কমান।
2.1. C-বিশেষ অনুমান — ভাষা আপনাকে কিছু থেকে রক্ষা করে না
C-তে ভাষার এই নীতি জবরদস্তি করার ব্যবস্থা নেই বলে এগুলো শৃঙ্খলা হিসেবে স্পষ্ট লিখতে হয়।
প্রথম, মুক্তির গ্যারান্টি কাঠামোতে গড়ুন। C++-এর RAII-এর সমতুল্য কিছু নেই বলে লক মুক্তি ও হ্যান্ডেলে CloseHandle রক্ষা করতে হয় goto cleanup প্যাটার্ন দিয়ে যা প্রতি ফাংশন বেরোনো এক জায়গা দিয়ে যায়, অথবা কোডিং রীতি দিয়ে যা প্রতি অর্জনকে মুক্তির সাথে জোড়ে। তাড়াতাড়ি return যোগ করে লক লিক করা C-এর ক্লাসিক দুর্ঘটনা।
দ্বিতীয়, ডেটা রেসকে C++-এর মতো ধরুন। সঠিকভাবে সারিবদ্ধ ৩২-বিট ভেরিয়েবলের সাধারণ পড়া বা লেখা Windows-এ পরমাণু, কিন্তু তার বেশি — ৩২-বিট Windows-এ ৬৪-বিট ভেরিয়েবল, যৌগিক অপারেশন, বা একাধিক ভেরিয়েবলের সামঞ্জস্য — কিছুই গ্যারান্টি নয়।12 «কাকতালীয়ভাবে চলে» এমন কোড কম্পাইলার বা অপটিমাইজেশন স্তর বদলালেই ভাঙে।
তৃতীয়, মালিকানা ঠিক করুন। ফাংশন মন্তব্যে «এই বাফার কোন থ্রেড লেখে, আর কখন থেকে কার» স্পষ্ট করার সংস্কৃতি C মাল্টিথ্রেডিংয়ে সিঙ্ক্রোনাইজেশন প্রিমিটিভ বাছার মতোই ফল দেয়।
3. থ্রেড কীভাবে তৈরি — _beginthreadex, আর কিছু নয়
3.1. CreateThread ভুল পছন্দ কেন
Win32-এর নেটিভ API CreateThread, কিন্তু সরকারি নির্দেশ যে CRT (C রানটাইম) ফাংশন ডাকা যেকোনো থ্রেড _beginthreadex দিয়ে তৈরি করতে হবে। _beginthreadex থ্রেড শুরু করার আগে CRT-এর প্রতি-থ্রেড অভ্যন্তরীণ ডেটা আরম্ভ করে। CreateThread দিয়ে তৈরি থ্রেড CRT ফাংশন ডাকলে কম-মেমরি অবস্থায় CRT প্রক্রিয়া শেষ করতে পারে।12 printf, malloc ও strtok সব CRT ফাংশন বলে ব্যবহারিক নিয়ম: «C-তে লেখা থ্রেড সবসময় _beginthreadex ব্যবহার করে»।
_beginthread (ex ছাড়া)ও এড়িয়ে চলুন। তাতে ফাঁদ: তৈরি থ্রেড তাড়াতাড়ি শেষ হলে ফেরত হ্যান্ডেল ইতিমধ্যে অবৈধ হতে পারে — এমনকি অন্য থ্রেডের দিকেও — আর _beginthreadex, যার হ্যান্ডেল সিঙ্ক্রোনাইজেশন API-তে নিরাপদে দেওয়া যায়, নিরাপদ পছন্দ। কলার _beginthreadex-এর ফেরত হ্যান্ডেল CloseHandle দিয়ে বন্ধ করে।13
#include <process.h>
static unsigned __stdcall WorkerMain(void* arg)
{
WorkerContext* ctx = (WorkerContext*)arg;
/* ... অধ্যায় 5 ও 6-এর অপেক্ষা লুপ ... */
return 0;
}
HANDLE hThread = (HANDLE)_beginthreadex(
NULL, 0, WorkerMain, &ctx, 0, NULL);
if (hThread == NULL) { /* ব্যর্থতা সামলানো */ }
/* ...স্টপ অনুরোধের পর... */
WaitForSingleObject(hThread, INFINITE); /* জয়েন */
CloseHandle(hThread);
3.2. ছোট কাজ Windows থ্রেড পুলে যায়
«অনেক ছোট কাজ কোথাও ছুড়তে» চান বা «বারবার স্বল্পায়ু থ্রেড তৈরি-ধ্বংস» করছেন, নিজের থ্রেডের বদলে Windows থ্রেড পুল (Vista থেকে পাওয়া থ্রেড পুল API) ব্যবহার করুন। CreateThreadpoolWork দিয়ে ওয়ার্ক অবজেক্ট তৈরি করে SubmitThreadpoolWork দিয়ে জমা দিন, পুলের ওয়ার্কার থ্রেড কলব্যাক সমান্তরাল চালায়।8 থ্রেড সংখ্যার ব্যবস্থাপনা OS-এর, তৈরি-ধ্বংসের খরচ মিলিয়ে যায়। «নিজের থ্রেড তৈরি করবেন না» নীতির C উত্তর এটাই।
পুল ব্যবহারের শৃঙ্খলাও সরকারিভাবে নথিভুক্ত: পুলের থ্রেড কখনো TerminateThread / ExitThread দিয়ে শেষ করবেন না; কলব্যাকে বদলানো কোনো অবস্থা (TLS, থ্রেড অগ্রাধিকার ইত্যাদি) ফেরার আগে ফিরিয়ে দিন; আর অপেক্ষা হ্যান্ডেল পুল শেষ না করা পর্যন্ত বাঁচিয়ে রাখুন।9 আরও একটি ব্যবহারিক নোট: পুল কেবল ওয়ার্কার থ্রেডের সংখ্যা সীমায় — SubmitThreadpoolWork দিয়ে সারিতে না-চলা কলব্যাক সীমাহীন জমতে পারে। দীর্ঘ-চলা গঠনে জমা প্রক্রিয়াকরণকে ছাড়িয়ে গেলে অ্যাপ পাশে সেমাফোরের মতো প্রবেশ সীমাক, বা সীমাবদ্ধ সারি রাখুন, যাতে জমাদাতা পাশ পূর্ণ হলে অপেক্ষা করে বা প্রত্যাখ্যাত হয় (এটি ব্যাক-প্রেশার যাতে অতিরিক্ত ভার মেমরি সমস্যা না হয়, অধ্যায় 5-এর সারি নকশার একই নীতি)।
4. শেয়ার করা পরিবর্তনযোগ্য অবস্থা ন্যূনতম করুন — ভাগ, কেবল-পড়া ও হস্তান্তর
রেস কেবল তখনই হয় যখন «একাধিক থ্রেড» ও «শেয়ার করা পরিবর্তনযোগ্য ডেটা» দুটোই থাকে। সিঙ্ক্রোনাইজেশন প্রিমিটিভ বাছার (পরের অধ্যায়) আগে ভাবুন শেয়ার আদৌ কমানো যায় কিনা। তিন পরিবারের কৌশল আছে।
ভাগ করুন। সমান্তরাল সংকলনে প্রতি থ্রেড শেয়ার করা কাউন্টারে না লিখে প্রতি-থ্রেড স্থানীয় ভেরিয়েবল (বা প্রতি থ্রেডে বরাদ্দ বাফার)-এ উপযোগ তৈরি করুন, শেষে একবার InterlockedAdd-এর মতো কিছু দিয়ে মেলান। শেয়ার অবস্থায় লেখা «প্রতি পুনরাবৃত্তি» থেকে «প্রতি থ্রেডে একবার» নামে, সিঙ্ক্রোনাইজেশন খরচ ও প্রতিযোগিতার জানালা দুটোই ক্রমমাত্রায় ছোট হয়। অধ্যায় 2.1-এর মালিকানা শৃঙ্খলা — «এই বাফার কোন থ্রেডের» — ভাগের নকশা হয়ে যায়।
কেবল-পড়া করুন। স্টার্টআপে গড়া ও পরে না-বদলানো কনফিগারেশন ও টেবিল আরম্ভ শেষ হলে যেকোনো সংখ্যক থ্রেড থেকে পড়া নিরাপদ। হয় সব থ্রেড শুরুর আগে সব আরম্ভ শেষ করুন, অথবা অলস আরম্ভ লাগলে Win32-এর একবার আরম্ভ (InitOnceExecuteOnce) ব্যবহার করুন, আর সীমা — «কখন থেকে কেবল-পড়া» — কোডে স্পষ্ট করুন।3
হস্তান্তর করুন। দুই পাশ শেয়ার করা ভেরিয়েবল না ছুঁয়ে থ্রেডের মধ্যে ডেটা প্রবাহ প্রযোজক/ভোক্তা সারি দিয়ে চালান। C-তে বাস্তবায়ন ঠিক অধ্যায় 5-এর কন্ডিশন-ভেরিয়েবল প্যাটার্ন (সীমাবদ্ধ বৃত্তাকার বাফার প্লাস SleepConditionVariableCS), যা সরকারি কাজ-করা উদাহরণ; ধারণক্ষমতার সীমাও স্বাভাবিক ব্যাক-প্রেশার দেয় — উৎপাদন ভোগকে ছাড়ালে অপেক্ষা করে।5
5. সিঙ্ক্রোনাইজেশন অবজেক্ট বাছা ও লক শৃঙ্খলা
Win32-এ অনেক ধরনের সিঙ্ক্রোনাইজেশন প্রিমিটিভ আছে, ভুল বাছা কর্মক্ষমতা ও সঠিকতা দুটোরই দাম নেয়। সরকারি নির্দেশ এক ছবিতে সারাংশ।3
flowchart TB
S{"প্রক্রিয়া পেরিয়ে<br/>সিঙ্ক্রোনাইজেশন লাগে?"} -->|"হ্যাঁ"| Q2{"কী জন্য?"}
Q2 -->|"পারস্পরিক বর্জন"| MTX["নামযুক্ত Mutex"]
Q2 -->|"সমবর্তী অ্যাক্সেস সংখ্যা সীমা"| SEM["নামযুক্ত সেমাফোর"]
Q2 -->|"ইভেন্ট বিজ্ঞপ্তি"| EVT["নামযুক্ত ইভেন্ট"]
S -->|"না - প্রক্রিয়া-অভ্যন্তর"| Q3{"একই থ্রেডের পুনরাবৃত্ত<br/>অর্জন লাগে?"}
Q3 -->|"হ্যাঁ"| CS["CRITICAL_SECTION"]
Q3 -->|"না"| Q4{"পোর্টেবল C++ কোড<br/>অগ্রাধিকার?"}
Q4 -->|"হ্যাঁ"| STD["std::mutex /<br/>std::shared_mutex"]
Q4 -->|"না"| SRW["SRW লক - ডিফল্ট পছন্দ"]
চিত্র 3: Win32 সিঙ্ক্রোনাইজেশন প্রিমিটিভ কীভাবে বাছবেন। প্রথম শাখা «প্রক্রিয়া পেরোয় কিনা» — মূল কথা পেরোয় না তো কার্নেল অবজেক্ট (Mutex) ধরবেন না
| প্রিমিটিভ | পরিসর | বৈশিষ্ট্য | কোথায় ব্যবহার |
|---|---|---|---|
| SRW লক | প্রক্রিয়া-অভ্যন্তর | দ্রুত (সাধারণত পুরোটা ইউজার মোডে থাকে), পয়েন্টার আকার, অপুনরাবৃত্ত | নতুন কোডের ডিফল্ট। AcquireSRWLockShared শেয়ার করা পড়াও দেয় |
| CRITICAL_SECTION | প্রক্রিয়া-অভ্যন্তর | দ্রুত (ঘোরে, তারপর কার্নেল অপেক্ষায় পড়ে), পুনরাবৃত্ত | একই থ্রেডের পুনরাবৃত্ত অর্জন লাগলে |
| Mutex | প্রক্রিয়া-অভ্যন্তর / প্রক্রিয়া পেরিয়ে | সবসময় কার্নেল অবজেক্ট, তাই ধীর | প্রক্রিয়া পেরিয়ে বর্জন (নামযুক্ত), বা WaitForMultipleObjects-এর সাথে |
| সেমাফোর | প্রক্রিয়া-অভ্যন্তর / প্রক্রিয়া পেরিয়ে | কার্নেল অবজেক্ট | রিসোর্স পুলে সমবর্তী অ্যাক্সেস সীমা |
| ইভেন্ট | প্রক্রিয়া-অভ্যন্তর / প্রক্রিয়া পেরিয়ে | কার্নেল অবজেক্ট | জানানো «কিছু ঘটেছে» (ডেটা রক্ষায় নয়) |
| Interlocked ফাংশন | প্রক্রিয়া-অভ্যন্তর (শেয়ার মেমরিতে প্রক্রিয়া পেরিয়েও) | লক-মুক্ত পরমাণু অপারেশন | কাউন্টার, ফ্ল্যাগ, পয়েন্টার বদল4 |
সারণি ও ফ্লোচার্টের এক পাদটীকা। ইভেন্ট, সেমাফোর ও মিউটেক্সের মতো কার্নেল অবজেক্ট নাম ছাড়া তৈরি করলে প্রক্রিয়া-অভ্যন্তর সিঙ্ক্রোনাইজেশনে একেবারে কাজ করে (অধ্যায় 6-এর স্টপ ইভেন্ট ঠিক নামহীন ইভেন্ট)। কার্নেল অবজেক্ট মানে কেবল প্রক্রিয়া পেরিয়ে নয়। উল্টো «নামহীন» কঠোর অর্থে «এক প্রক্রিয়ায় বন্দি»ও নয় — চাইল্ড প্রক্রিয়াকে হ্যান্ডেল উত্তরাধিকার দিলে, বা DuplicateHandle দিয়ে অন্য প্রক্রিয়ায় নকল করলে, নাম ছাড়াও একই কার্নেল অবজেক্ট একাধিক প্রক্রিয়া থেকে ব্যবহার হয়। সঠিক কথা নামকরণ প্রক্রিয়াগুলোকে একই অবজেক্ট আবার খোলার একটি প্রতিনিধি উপায়। চিত্র 3-এর শাখা পছন্দের মূল কথা ধরে «প্রক্রিয়া-অভ্যন্তর লকের জন্য কার্নেল অবজেক্ট বাছবেন না» — প্রক্রিয়া-অভ্যন্তর বিজ্ঞপ্তি (ইভেন্ট) বা সমবর্তীতা সীমা (সেমাফোর)-এ নামহীন কার্নেল অবজেক্টই সঠিক উত্তর।
Interlocked পরিবার .NET সংস্করণের Interlocked ক্লাস ও C++ সংস্করণের std::atomic-এর সমতুল্য। InterlockedIncrement / InterlockedExchange / InterlockedCompareExchange এক ভেরিয়েবলে অপারেশন অবিভাজ্য করে, আর এগুলোর বেশিরভাগ পূর্ণ মেমরি ব্যারিয়ার বহন করে বলে ক্রমের গ্যারান্টিও পাওয়া যায়।4 «ঠিক আছে কারণ volatile লাগিয়েছি» ভুল ধারণা — volatile পরমাণুতাও গ্যারান্টি দেয় না, ক্রমও না (FAQ দেখুন)। আর একটি পূর্বশর্ত: সারিবদ্ধতা। Interlocked ফাংশনের লক্ষ্য ভেরিয়েবল স্বাভাবিক সীমায় সারিবদ্ধ হতে হবে (৩২-বিট মানে ৪-বাইট সীমা, ৬৪-বিটে ৮-বাইট); না হলে আচরণ অনির্দেশ্য।12 #pragma pack করা স্ট্রাক্টের ফিল্ড, বা তার ফরম্যাটে সরাসরি ম্যাপ বাফারের ফিল্ড কখনো Interlocked-এর লক্ষ্য করবেন না। কাউন্টার ও ফ্ল্যাগ সাধারণ ঘোষিত ভেরিয়েবলে সীমাবদ্ধ রাখুন — যেগুলো কম্পাইলার আপনার হয়ে সারায়। পয়েন্টার বদল যেমন InterlockedExchangePointer-এর বিশেষ সতর্কতাও আছে: অবিভাজ্য কেবল বদল নিজে, আর বদলের পর পুরনো ব্লকের আয়ু কেউ গ্যারান্টি দেয় না। পাঠক পুরনো পয়েন্টার লোড করলে ঠিক যখন লেখক বদলে free ডাকে, তা মুক্ত মেমরিতে প্রবেশ। পয়েন্টার বদলে শেয়ার ডেটা আপডেটের নকশা কেবল পুনরুদ্ধার প্রোটোকল — লক, রেফারেন্স গণনা বা সমতুল্য — এর সাথে জোড়া থাকলে চলে (সন্দেহ হলে SRW লক দিয়ে রক্ষা নিরাপদ ডিফল্ট)।
কিছুর অপেক্ষায় কন্ডিশন ভেরিয়েবল আছে। InitializeConditionVariable দিয়ে একটি তৈরি করুন; ভোক্তা SleepConditionVariableCS-এ ঘুমায় (CRITICAL_SECTION-এর সাথে জোড়া), প্রযোজক WakeConditionVariable দিয়ে জাগায় — সীমাবদ্ধ বাফারে সরকারি প্রযোজক/ভোক্তা সারির উদাহরণ ঠিক এই আকার।5 গুরুত্বপূর্ণ শৃঙ্খলা: জাগলে লকের ভিতরে শর্ত (সারি খালি নয় তো) সবসময় আবার দেখুন, মিথ্যা হলে অপেক্ষায় ফিরুন। কন্ডিশন ভেরিয়েবলে কোনো বিজ্ঞপ্তি ছাড়াই স্পিউরিয়াস ওয়েকআপ হতে পারে, আর জাগার বেলা অন্য ভোক্তা আগে আইটেম নিয়ে যেতে পারে — তাই «আমাকে জাগানো হয়েছে» মানেই «শর্ত সত্য» নয়। এটি .NET সংস্করণ অধ্যায় 4.3-এর চ্যানেল ও C++ সংস্করণ অধ্যায় 4-এর BlockingQueue-এর একই আকার C-তে গড়ার সরঞ্জাম। SRW লকের সাথে জোড়লে SleepConditionVariableSRW ব্যবহার করুন।
5.1. লক শৃঙ্খলা — তিন নীতি যেকোনো প্রিমিটিভ বাছলেও দাঁড়ায়
সঠিক প্রিমিটিভ বাছা একা যথেষ্ট নয় — শৃঙ্খলিত ব্যবহার ছাড়া রেস তবু ঠেকাবেন না।
- এক-এক করে ঠিক করুন কোন লক কোন ডেটা রক্ষা করে। রক্ষা করতে চান এমন প্রতি পরিবর্তনযোগ্য ডেটা সেটে ঠিক এক লক (SRW লক বা CRITICAL_SECTION) দিন, আর সেই ডেটা ছোঁয়া প্রতি জায়গায় সেই একই লক নিন। C-তে বিশেষ করে হেডার মন্তব্যে স্পষ্ট লেখা ফল দেয় — «এই স্ট্রাক্ট
g_lockFooরক্ষা করে»। - লক ধরে কিছু ধীর বা বাইরের করবেন না। লক ধরে কেবল সেই ডেটা পড়া বা লেখা ঠিক যা সে রক্ষা করে। লক ধরে ফাইল I/O, নেটওয়ার্ক কল বা কলব্যাক ডাক ধরার সময় বাড়ায় আর ঝুঁকি যে ডাকা পক্ষ অন্য লক নিতে যাবে, চিত্র 2-এর বৃত্তাকার অপেক্ষা তৈরি করে।
- একাধিক লকের অর্জন ক্রম স্থির করুন। যেখানে দুই বা তার বেশি লক নেওয়া হয়, নিয়ম করুন প্রতি থ্রেড একই ক্রমে নেয় (লক অনুক্রম)। DLL সেরা-অনুশীলন নথি স্পষ্ট বলে সেই ক্রম উল্টানো (lock order inversion) ডিবাগ-কঠিন ডেডলক জন্মায়, আর অনুক্রম সংজ্ঞায়িত করে ধারাবাহিক মেনে চলতে হবে।10
6. থামানোর নকশা — কখনো TerminateThread নয়
6.1. TerminateThread কী ভাঙে
TerminateThread লক্ষ্য থ্রেডকে কোনো ইউজার-মোড কোড চালাতে না দিয়ে মুছে দেয়। সরকারি নথি যে ফল গণনা করে তা গুরুতর। লক্ষ্য থ্রেড ক্রিটিক্যাল সেকশন ধরে থাকলে তা কখনো ছাড়ে না; হিপ অপারেশনের মাঝে থাকলে হিপ লক ধরা থাকে (পরের প্রতি থ্রেড যে malloc ডাকে আটকে যায়); আর কোনো DLL-এর গ্লোবাল অবস্থা বদলাচ্ছে তো সেই অবস্থা নষ্ট থেকে যায়। সরকারি অবস্থান এটি «একটি বিপজ্জনক ফাংশন যা কেবল সবচেয়ে চরম ক্ষেত্রে ব্যবহার করা উচিত», আর কোড বিশ্লেষণ সতর্কতা C6258 দেয়।67
«মাঝে মাঝে পুরোপুরি আটকে যায়» এমন অ্যাপ তদন্তে TerminateThread পাওয়া বাস্তবে সত্যিই সাধারণ দৃশ্য। পেলে মেরামতের বিষয় ধরুন।
6.2. সঠিক প্যাটার্ন: স্টপ ইভেন্ট প্লাস WaitForMultipleObjects
C-তে সহযোগী শাটডাউনের স্থিত প্যাটার্ন একটি ম্যানুয়াল-রিসেট স্টপ ইভেন্ট তৈরি করা, আর প্রতি ওয়ার্কার থ্রেডকে «কাজের সংকেত» ও «থামার সংকেত» একসাথে অপেক্ষা করানো। C6258 সতর্কতার নথি নিজেই ঠিক এই প্যাটার্নের দিকে ইঙ্গিত করে — ইভেন্ট তৈরি করুন, প্রতি থ্রেড WaitForSingleObject দিয়ে দেখুক, থ্রেড নিজে শেষ হোক — সঠিক সমাপ্তি হিসেবে।7
HANDLE hStopEvent; /* CreateEvent(NULL, TRUE, FALSE, NULL): ম্যানুয়াল-রিসেট */
HANDLE hWorkEvent; /* CreateEvent(NULL, FALSE, FALSE, NULL): অটো-রিসেট.
পাওয়া মাত্র স্বয়ংক্রিয় অসংকেতিতে ফেরে
(ম্যানুয়াল-রিসেট ইভেন্ট একবার সংকেতের পর
অপেক্ষা সোজা পার করত, খালি সারিতে
ঘোরা ব্যস্ত লুপ হত) */
static unsigned __stdcall WorkerMain(void* arg)
{
HANDLE waits[2] = { hStopEvent, hWorkEvent };
for (;;) {
DWORD r = WaitForMultipleObjects(2, waits, FALSE, INFINITE);
if (r == WAIT_FAILED) { /* যেমন অবৈধ হ্যান্ডেল. না সামলালে খালি ঘোরে */
LogLastError(); /* GetLastError() লিখে বেরোন */
break;
}
if (r == WAIT_OBJECT_0) /* স্টপ অনুরোধ */
break;
if (r == WAIT_OBJECT_0 + 1) { /* কাজ আছে */
/* ProcessNextItem-এও স্টপ ইভেন্ট দিন: ভিতরে এক আইটেমের জন্য
দীর্ঘ অপেক্ষা করলে, আর সেখানেও স্টপ না দেখতে পারলে,
শাটডাউন সেই এক আইটেমের জিম্মি হয় */
while (ProcessNextItem(hStopEvent)) { /* সারি থেকে এক আইটেম প্রক্রিয়া; খালি হলে FALSE */
/* খালানোর সময়ও স্টপ অনুরোধ দেখুন. এটা এড়ালে কাজ
জমতে থাকলে থামতে পারবেন না (স্টপ অনাহার) */
if (WaitForSingleObject(hStopEvent, 0) == WAIT_OBJECT_0)
break;
}
}
}
Cleanup(); /* নিজের পরিষ্কার নিজে করুন */
return 0; /* নিজে শেষ হোন */
}
BOOL StopWorkers(HANDLE* threads, DWORD count)
{
BOOL ok = TRUE;
if (!SetEvent(hStopEvent)) { /* স্টপ অনুরোধ না পৌঁছালে, */
LogLastError(); /* সীমাহীন জয়েনে যাবেন না */
return FALSE;
}
/* এখন সবার স্টপ অনুরোধ একসাথে তোলা */
for (DWORD i = 0; i < count; i++) {
if (WaitForSingleObject(threads[i], INFINITE) == WAIT_OBJECT_0) {
CloseHandle(threads[i]); /* কেবল যে হ্যান্ডেলের জয়েন নিশ্চিত সেগুলো বন্ধ */
} else {
LogLastError(); /* WAIT_FAILED: যেমন অবৈধ হ্যান্ডেল */
ok = FALSE; /* «সবাই থেমেছে» বলবেন না */
}
}
return ok; /* FALSE হলে শেয়ার রিসোর্স ছাড়তে এগোবেন না */
}
থামাবার পক্ষ WaitForSingleObject দিয়ে এক-এক থ্রেড জয়েন করার কারণ আছে। WaitForMultipleObjects একসাথে সর্বোচ্চ MAXIMUM_WAIT_OBJECTS (64) হ্যান্ডেল অপেক্ষা করতে পারে; বড় অ্যারে দিলে অপেক্ষা নিজে WAIT_FAILED দিয়ে ব্যর্থ হয়, আপনি হ্যান্ডেল বন্ধ করেন ভেবে সবার অপেক্ষা করেছেন অথচ কারও করেননি। সবার শেষ হওয়ার অপেক্ষাই চাইলে উপরের সীমাহীন এক-এক লুপ নিরাপদ পছন্দ।
flowchart TB
OWNER["থামাবার পক্ষ"] -->|"SetEvent(hStopEvent)"| SE["স্টপ ইভেন্ট<br/>ম্যানুয়াল-রিসেট - সবাই একসাথে দেখে"]
SE --> W1["ওয়ার্কার 1 - স্টপ ও কাজ একসাথে<br/>WaitForMultipleObjects দিয়ে অপেক্ষা"]
SE --> W2["ওয়ার্কার 2 - স্টপ ও কাজ একসাথে<br/>WaitForMultipleObjects দিয়ে অপেক্ষা"]
W1 --> C1["পরিষ্কার করে নিজে ফেরে"]
W2 --> C2["পরিষ্কার করে নিজে ফেরে"]
C1 --> J["থামাবার পক্ষ থ্রেড হ্যান্ডেল অপেক্ষা করে জয়েন করে<br/>এখনই থেমেছে বলা যায়"]
C2 --> J
চিত্র 4: স্টপ-ইভেন্ট প্যাটার্ন। স্টপ সংকেতে ম্যানুয়াল-রিসেট ইভেন্ট মানে এক SetEvent অপেক্ষমাণ প্রতি ওয়ার্কারকে একসাথে জাগায়। প্রতি থ্রেড নিজে ঠিক করে কীভাবে শেষ হয়, আর থামা কেবল জয়েন শেষ হলে সম্পূর্ণ ধরা হয়
তিন মূল কথা। স্টপ ইভেন্ট ম্যানুয়াল-রিসেট করুন (এক SetEvent প্রতি ওয়ার্কার দেখে); অপেক্ষা অ্যারেতে স্টপ ইভেন্ট আগে রাখুন (দুটো একসাথে সংকেতিত হলে স্টপ অগ্রাধিকার পায়); থামাবার পক্ষ সবসময় হ্যান্ডেল বন্ধের আগে থ্রেড হ্যান্ডেল জয়েন করুক।
পরিসরের দুই সতর্কতা। প্রথম, এই «ইভেন্ট প্লাস সব-খালি» প্যাটার্ন এক-ওয়ার্কার গঠনের জন্য। অটো-রিসেট ইভেন্টে যতবারই SetEvent ডাকুন, কেবল «এক সংকেতিত অবস্থা আছে» প্রকাশ করতে পারে (ধারাবাহিক সংকেত মিশে যায়), তাই একাধিক ওয়ার্কারে একজন জাগে আর পুরো ঝাঁক ক্রমান্বয়ে প্রক্রিয়া করে। একাধিক ওয়ার্কার সারি ভাগ করলে কাজের সংকেত সেমাফোরে বদলান, প্রতি আইটেম সারিতে রাখলে ReleaseSemaphore(hSem, 1, NULL) দিয়ে গণনা বাড়ান। সফল সেমাফোর অপেক্ষা এক গণনা খায়, সঠিক মিল দেয়: সারিতে যত আইটেম তত অপেক্ষমাণ ওয়ার্কার এক-এক করে জাগে (এই ব্যবহার সেমাফোরের এলাকায়, চিত্র 3 সারণির «রিসোর্স পুলে সমবর্তী অ্যাক্সেস সীমা»র মতো)। কিন্তু সেমাফোরে গেলে ভোক্তা পাশও বদলান যাতে এক সফল অপেক্ষা ঠিক এক আইটেম প্রক্রিয়ার সমান হয়। উপরের নমুনার সব-খালি লুপ রেখে দিলে এক অপেক্ষা — যা কেবল এক অনুমতি খায় — পুরো সারি খালি করবে, হিসাব ভাঙবে: বাকি অনুমতিতে অন্য ওয়ার্কার খালি সারিতে জাগে, প্রযোজকের ReleaseSemaphore সর্বোচ্চ গণনা ছাড়িয়ে ব্যর্থ হতে শুরু করে। «এক অনুমতি সমান এক কাজ» মিল রাখা সেমাফোর পথের পূর্বশর্ত। দ্বিতীয়, স্টপ পথ এক আইটেম প্রক্রিয়ার মধ্য দিয়েও চালান। ProcessNextItem ভিতরে দীর্ঘ ব্লকিং অপেক্ষা করলে হয় সেখানেও স্টপ ইভেন্ট দিয়ে দুটো একসাথে অপেক্ষা করুন, নয়ত সীমিত টাইমআউট লাগান। কেবল আইটেমের মাঝে দেখা «এক আইটেম শেষ না হওয়ায় শাটডাউন চিরকাল অপেক্ষা করে» ফাঁক রাখে। এটা ঠিক যা .NET সংস্করণের StopAsync ও C++ সংস্করণের jthread প্লাস জয়েন বলে।
ব্লকিং I/O (পাইপ, সকেট, সিরিয়াল পোর্ট)-এ অপেক্ষমাণ থ্রেড ইভেন্ট দেখতে ফিরতে পারে না, তাই I/O পাশের নিজস্ব নকশা লাগে — হয় OVERLAPPED I/O ইভেন্টের সাথে যা একসাথে অপেক্ষা করেন, নয়ত CancelIoEx দিয়ে I/O জাগান (সিরিয়ালের বাস্তব উদাহরণ “Serial Communication App Pitfalls - Through Reconnection and Log Design”)।
7. DllMain ও লোডার লক — DLL লেখকের মাইনক্ষেত্র
C-তে লেখা শেয়ার কম্পোনেন্ট প্রায়ই DLL হয়, আর DLL-এর নিজস্ব সীমা: লোডার লক। OS লোডার DllMain ডাকে লোডার লক ধরে, তাই এর ভিতরে নিচের যেকোনোটি ডেডলক বা ক্র্যাশের উৎস হয়।10
- অন্য থ্রেডের সাথে সিঙ্ক্রোনাইজ (লক নেওয়া, থ্রেড শেষ হওয়ার অপেক্ষা)
LoadLibrary/FreeLibraryডাকা, সরাসরি বা পরোক্ষ- থ্রেড তৈরি (সিঙ্ক্রোনাইজেশন থাকলে বিপজ্জনক), বা
ExitThreadডাকা
«DLL আনলোডে ওয়ার্কার থ্রেড শেষ হওয়ার DllMain-এ অপেক্ষা» একেবারে যৌক্তিক দেখায় কিন্তু ক্লাসিক ডেডলক: শেষ হওয়া থ্রেড DLL_THREAD_DETACH পৌঁছাতে লোডার লক নিতে যায়, দুই পাশ একে অপরের অপেক্ষা করে। নিজের থ্রেডওয়ালা DLL-কে স্পষ্ট আরম্ভ ও শাটডাউন ফাংশন — যেমন MyLib_Init / MyLib_Shutdown — প্রকাশ করতে হবে, থ্রেড স্টার্ট ও জয়েন সেখানে করতে হবে। আদর্শ DllMain খালি স্টাবের কাছাকাছি।10
8. C11 থ্রেড বিকল্প — কোথায় দাঁড়িয়ে
Win32-নির্ভর নয় এমন পোর্টেবল C লিখতে চাইলে বিকল্প C11-এর <threads.h> (thrd_create / mtx_lock / cnd_wait) ও <stdatomic.h>। সরকারি সামঞ্জস্য সারণি অনুসারে MSVC সমর্থন এমন: <threads.h> Visual Studio 2022 17.8 থেকে সমর্থিত (/std:c11 ও মিল Windows SDK লাগে), আর <stdatomic.h> এখনও পরীক্ষামূলক, /experimental:c11atomics অপশন চাওয়ার পর্যায়ে।11
Linux-এর সাথে কোড ভাগ প্রয়োজন হলে C11 থ্রেড (বা pthread র্যাপার)-এর আসল মূল্য আছে, কিন্তু কেবল-Windows কোডবেসের জন্য এই নিবন্ধের Win32 পথ তথ্যের পরিমাণ, ট্র্যাক রেকর্ড ও ডিবাগ সহজে এগিয়ে। যাই বাছুন, এখন পর্যন্ত নকশা নীতি — শেয়ার কমানো, লক ও ডেটার মিল, সহযোগী শাটডাউন — বদলায় না।
9. যাচাই ও ডিবাগ — «পুনরুৎপাদন হয় না»-র প্রস্তুতি
রেস বাগ ধরার আশা পরীক্ষা থেকে করা যায় না। সাধারণ পরীক্ষা সেই রানকে পাস গণে যেখানে রেস «কাকতালীয়ভাবে চলেনি»। প্রতিরক্ষা তিন স্তরে ভাবুন।
প্রথম সারি ডিজাইন। রিভিউতে সারণি দিয়ে নিশ্চিত করুন: কোন পরিবর্তনযোগ্য ডেটা শেয়ার, কোন লক প্রতি টুকরো রক্ষা করে (অধ্যায় 5.1-এর মিল), লক অর্জন ক্রম দ্ব্যর্থহীন কিনা, স্টপ ইভেন্ট প্রতি ওয়ার্কারে পৌঁছায় কিনা। যে নকশা এই সারণি পূরণ করতে পারে না এখনও শেষ নয়, এখন চললেও।
দ্বিতীয়, অস্বাভাবিকতা পর্যবেক্ষণযোগ্য করুন। INFINITE দিয়ে শর্তহীন অপেক্ষার বদলে মূল জায়গায় টাইমআউট লাগান আর টাইমআউট ফায়ার হলে লগ করুন, চিরকাল চলা হ্যাংকে শনাক্তযোগ্য ব্যর্থতায় বদলে। মাঠে হ্যাং হলে ডাম্প নিন, প্রতি থ্রেডের স্ট্যাক দেখুন, চক্র খুঁজুন কে কার লকের অপেক্ষা করছে। DLL ঘিরে ভুলে Application Verifier দিয়ে পরীক্ষা সরকারিভাবে সুপারিশকৃত।10 ডাম্প ও লগ গড়া “Designing Windows Apps to Leave Logs and Dumps When They Crash“-এ আছে।
তৃতীয়, ভারে নাড়ুন। স্ট্রেস পরীক্ষা — কোরের চেয়ে বেশি থ্রেডে চালানো, প্রক্রিয়াকরণ ক্রম এলোমেলো, কৃত্রিম বিলম্ব ঢোকানো — ডেভেলপমেন্ট মেশিনে রেস «জ্যাকপট» লাগার সম্ভাবনা বাড়ানোর ব্যবহারিক উপায়। অপটিমাইজড রিলিজ বিল্ডে ভারী ভারে পুনরুৎপাদনও ভুলবেন না।
10. সারসংক্ষেপ — C সংস্করণ চেকলিস্ট
- প্রতি থ্রেড
_beginthreadexদিয়ে তৈরি (CreateThread/_beginthreadমেশানো নেই)? CloseHandle-এর আগে থ্রেড হ্যান্ডেল জয়েন (WaitForSingleObject) করেন?- স্বল্পায়ু কাজে নিজের থ্রেড ঢালাও তৈরি করছেন (থ্রেড পুল API-তে দেওয়া যায়)?
- প্রক্রিয়া-অভ্যন্তর বর্জন SRW লক / CRITICAL_SECTION ব্যবহার করে (Mutex অপব্যবহার নয়)?
- শেয়ার করা কাউন্টার ও ফ্ল্যাগ
volatile-এর উপর নির্ভর না করে Interlocked দিয়ে আপডেট হয়? - কোনো
Sleepপোলিং বাকি (কন্ডিশন ভেরিয়েবল বা ইভেন্ট অপেক্ষায় বদলেছে)? TerminateThread(অন্য থ্রেড জোর করে মারা) সব জায়গায় অনুপস্থিত? ওয়ার্কারExitThreadনা ডেকে থ্রেড ফাংশন থেকেreturnদিয়ে শেষ হয় (যাতে CRT পরিষ্কার_endthreadexদিয়ে সঠিক চলে)?- প্রতি ওয়ার্কারের স্টপ ইভেন্ট প্লাস
WaitForMultipleObjects-এর স্টপ পথ আছে, আর I/O-তে ব্লক থ্রেডও জাগাতে পারেন? - প্রতি ফেরার পথে লক ও হ্যান্ডেল মুক্তি গ্যারান্টি (
goto cleanupশৃঙ্খলা)? DllMainথ্রেড তৈরি, সিঙ্ক্রোনাইজ বা থ্রেড শেষ হওয়ার অপেক্ষা এড়ায়?
ভাষার সাহায্য না থাকার বদলে C-তে মাল্টিথ্রেডিংয়ের গুণ ঠিক যা আপনার API পছন্দ ও শৃঙ্খলা তৈরি করে। _beginthreadex, SRW লক, Interlocked ফাংশন ও স্টপ ইভেন্টকে ডিফল্ট চার-টুকরো সেট করুন, C-তেও «মাঝে মাঝে আটকে যায়» থেকে দূরে নকশা করা যায়।
সম্পর্কিত নিবন্ধ
- ব্যবহারিক মাল্টিথ্রেডিং সেরা অনুশীলন: .NET সংস্করণ — আরও থ্রেড যোগ করার আগে কী ঠিক করবেন
- ব্যবহারিক মাল্টিথ্রেডিং সেরা অনুশীলন: C++ সংস্করণ — RAII ও jthread দিয়ে কাঠামোতে দুর্ঘটনা মুছে ফেলা
- ব্যবহারিক মাল্টিথ্রেডিং সেরা অনুশীলন: Java সংস্করণ — ভার্চুয়াল থ্রেড যুগের নিয়মাবলি
- Shared Memory Pitfalls and Practical Best Practices
- Why You Should Prefer Event Waits over Sleep(1) on Windows
- Serial Communication App Pitfalls - Through Reconnection and Log Design
- Designing Windows Apps to Leave Logs and Dumps When They Crash
সম্পর্কিত পরামর্শ ক্ষেত্র
KomuraSoft LLC C-তে লেখা আবাসিক প্রক্রিয়া, যন্ত্র-নিয়ন্ত্রণ অ্যাপ ও DLL-এর মাল্টিথ্রেডিং ডিজাইন রিভিউ; TerminateThread বা লিক লক থেকে হ্যাং ও ক্র্যাশের মূল কারণ তদন্ত (ডাম্প বিশ্লেষণ); আর পুরনো C কোডে থ্রেড যোগের প্রযুক্তিগত পরামর্শ সামলায়।
তথ্যসূত্র
-
Microsoft Learn, CreateThread function। CRT ডাকা এক্সিকিউটেবলের ভিতরের থ্রেড CreateThread / ExitThread নয় _beginthreadex / _endthreadex দিয়ে চালাতে হবে, আর CreateThread দিয়ে তৈরি থ্রেড CRT ডাকলে কম মেমরিতে CRT প্রক্রিয়া শেষ করতে পারে। ↩ ↩2
-
Microsoft Learn, Multithreading with C and Win32। CRT লাইব্রেরি ডাকা প্রোগ্রাম থ্রেড _beginthread / _beginthreadex দিয়ে শুরু করবে Win32 CreateThread / ExitThread দিয়ে নয়; _beginthread পরিবার CRT-এর প্রতি-থ্রেড ভেরিয়েবল আরম্ভ করে; আর SuspendThread CRT অভ্যন্তর ডেটা স্ট্রাকচারে প্রবেশকালে থ্রেড থামাতে পারে, যা ডেডলক আনতে পারে। ↩ ↩2
-
Microsoft Learn, About Synchronization। Win32 সিঙ্ক্রোনাইজেশন প্রিমিটিভ বাছার নির্দেশ: নতুন কোডের ডিফল্ট SRW লক, পয়েন্টার আকার ও সাধারণত ইউজার মোডে; পুনরাবৃত্ত অর্জন লাগলে CRITICAL_SECTION; Mutex সবসময় কার্নেল অবজেক্ট, নামযুক্ত প্রক্রিয়া-পার সিঙ্ক্রোনাইজেশন ও WaitForMultipleObjects-এর সাথে; প্রক্রিয়া-অভ্যন্তর সিঙ্ক্রোনাইজেশনে Mutex ঘন অপারেশনে অনেক ধীর «সাধারণ ভুল»; সেমাফোর রিসোর্স পুল সমবর্তী অ্যাক্সেস সীমায়, ইভেন্ট বিজ্ঞপ্তিতে। ↩ ↩2 ↩3
-
Microsoft Learn, Interlocked Variable Access। Interlocked ফাংশন একাধিক থ্রেডে শেয়ার ভেরিয়েবলের অ্যাক্সেস সিঙ্ক্রোনাইজ করে অপারেশন অবিভাজ্য করে; InterlockedIncrement / Decrement পড়া, যোগ ও ফিরিয়ে লেখা এক পরমাণু অপারেশনে বাঁধে, সিঙ্ক্রোনাইজেশন ছাড়া দুই থ্রেডের একসাথে বৃদ্ধি এক বৃদ্ধি হারাতে পারে; InterlockedExchange / InterlockedCompareExchange পরিবার; শেয়ার মেমরিতে ভেরিয়েবল থাকলে ভিন্ন প্রক্রিয়ার থ্রেডের মধ্যে ব্যবহার; বেশিরভাগ Interlocked পূর্ণ মেমরি ব্যারিয়ার দেয়, Acquire / Release রূপে ক্রম অর্থ বেছে নেওয়া যায়। ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Using Condition Variables। CRITICAL_SECTION দিয়ে রক্ষিত সীমাবদ্ধ বৃত্তাকার বাফারে প্রযোজক/ভোক্তা সারির কাজ-করা উদাহরণ; গঠন যেখানে InitializeConditionVariable কন্ডিশন ভেরিয়েবল তৈরি করে, ভোক্তা SleepConditionVariableCS দিয়ে অপেক্ষা করে, প্রযোজক WakeConditionVariable দিয়ে জাগায়; কন্ডিশন ভেরিয়েবল Windows Vista থেকে সমর্থিত। ↩ ↩2 ↩3
-
Microsoft Learn, TerminateThread function। TerminateThread লক্ষ্য থ্রেডকে কোনো ইউজার-মোড কোড চালাতে না দিয়ে শেষ করে; লক্ষ্যের ক্রিটিক্যাল সেকশন ধরে থাকলে ছাড়ে না; হিপ থেকে মেমরি বরাদ্দকালে হিপ লক ছাড়ে না; kernel32-এর অবস্থা বা DLL-এর গ্লোবাল অবস্থা নষ্ট হতে পারে; এটি «একটি বিপজ্জনক ফাংশন যা কেবল সবচেয়ে চরম ক্ষেত্রে ব্যবহার করা উচিত», লক্ষ্য থ্রেড যে কোড পথ চালাতে পারে তা পুরোপুরি না জানা ও নিয়ন্ত্রণ না করা পর্যন্ত ডাকবেন না। ↩ ↩2
-
Microsoft Learn, Warning C6258। কোড-বিশ্লেষণ সতর্কতা C6258 TerminateThread ব্যবহার ধরে; TerminateThread যথাযথ থ্রেড পরিষ্কার করতে পারে না; সঠিক সমাপ্তি পদ্ধতি CreateEvent দিয়ে ইভেন্ট তৈরি, প্রতি থ্রেড WaitForSingleObject দিয়ে ইভেন্ট অবস্থা দেখা, ইভেন্ট সংকেতিত হলে থ্রেড নিজে এক্সিকিউশন শেষ করা দেখানো হয়েছে। ↩ ↩2 ↩3
-
Microsoft Learn, CreateThreadpoolWork function। CreateThreadpoolWork দিয়ে ওয়ার্ক অবজেক্ট তৈরি ও প্রতি SubmitThreadpoolWork-এ পুল ওয়ার্কার থ্রেড কলব্যাক চালায়; কলব্যাক পরিবেশ (TP_CALLBACK_ENVIRON) দিয়ে এক্সিকিউশন পরিবেশ নির্দিষ্ট করা যায়; Windows Vista থেকে পাওয়া যায়। ↩ ↩2
-
Microsoft Learn, Thread Pools। থ্রেড পুল ছোট অ্যাসিঙ্ক্রোনাস কাজের বড় সংখ্যা চালানো বা ঘন ঘন স্বল্পায়ু থ্রেড তৈরি করা অ্যাপের উপযোগী; Vista-তে পুনর্নকশা নতুন থ্রেড পুল API-এর উপাদান; সেরা অনুশীলন পুলের থ্রেড TerminateThread দিয়ে শেষ না করা বা কলব্যাক থেকে ExitThread না ডাকা, কলব্যাকে তৈরি অবস্থা ফেরার আগে পরিষ্কার, অপেক্ষা হ্যান্ডেল পুল শেষ না করা পর্যন্ত বাঁচিয়ে রাখা। ↩ ↩2
-
Microsoft Learn, Dynamic-Link Library Best Practices। DllMain লোডার লক ধরে ডাকা হয়, কোন API নিরাপদে ডাকা যায় তাতে গুরুতর সীমা; DllMain-এ অন্য থ্রেডের সাথে সিঙ্ক্রোনাইজ ডেডলক আনে; LoadLibrary ডাকা নিষিদ্ধ কাজের তালিকায়; DLL আনলোডে DllMain-এ থ্রেড শেষ হওয়ার অপেক্ষা সেই থ্রেডের DLL_THREAD_DETACH পৌঁছাতে লোডার লক নেওয়ার চেষ্টার সাথে ডেডলক হয়; আদর্শ DllMain খালি স্টাবের কাছাকাছি, আরম্ভ যত সম্ভব দেরি; লোডার লক শীর্ষে লক অনুক্রম সংজ্ঞায়িত করা। ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Microsoft C/C++ language conformance by Visual Studio version। C স্ট্যান্ডার্ড লাইব্রেরি সামঞ্জস্য সারণি, C11 থ্রেড (threads.h) Visual Studio 2022 17.8 থেকে সমর্থিত; stdatomic.h পরীক্ষামূলক (/experimental:c11atomics অপশনের পেছনে); C11 / C17 কম্পাইলার সমর্থনে Visual Studio 2019 16.8 বা পরের সাথে মিল Windows SDK লাগে। ↩ ↩2
-
Microsoft Learn, Interlocked Variable Access। সঠিকভাবে সারিবদ্ধ ৩২-বিট ভেরিয়েবলের সাধারণ পড়া বা লেখা পরমাণু, কিন্তু অ্যাক্সেসের সিঙ্ক্রোনাইজেশন (ক্রম) গ্যারান্টি নয়; ৬৪-বিট ভেরিয়েবলের সাধারণ পড়া-লেখা ৬৪-বিট Windows-এ পরমাণু কিন্তু ৩২-বিট Windows-এ গ্যারান্টি নয়; অন্য আকারের ভেরিয়েবল কোনো প্ল্যাটফর্মে পরমাণু গ্যারান্টি নয়। ↩ ↩2
-
Microsoft Learn, _beginthread, _beginthreadex। কেন _beginthreadex _beginthread-এর চেয়ে নিরাপদ: _beginthread দিয়ে তৈরি থ্রেড তাড়াতাড়ি শেষ হলে ফেরত হ্যান্ডেল অবৈধ (বা অন্য থ্রেডের দিকে) থাকতে পারে; _beginthreadex-এর হ্যান্ডেল কলার CloseHandle দিয়ে বন্ধ করবে আর বৈধতা গ্যারান্টি; _beginthreadex হ্যান্ডেল সিঙ্ক্রোনাইজেশন API-তে দিতে দেয়; থ্রেড ফাংশন __stdcall কলিং কনভেনশনে থ্রেড প্রস্থান কোড ফেরায়; আর মাল্টিথ্রেডেড CRT-এর সাথে লিংক লাগে। ↩
সম্পর্কিত নিবন্ধ
কাছাকাছি বিষয়ে গভীরে যেতে একই ট্যাগযুক্ত সাম্প্রতিক নিবন্ধ।
ব্যবহারিক মাল্টিথ্রেডিং সেরা অনুশীলন: C++ সংস্করণ — RAII ও jthread দিয়ে কাঠামো থেকে দুর্ঘটনা মুছে ফেলা
C++-এ মাল্টিথ্রেডিং এমন জগৎ যেখানে ডেটা রেসই অনির্ধারিত আচরণ। এই নিবন্ধ std::thread-এর ডেস্ট্রাক্টরের ফাঁদ, jthread ও stop_token দিয়ে থা...
Win32 থ্রেড পুল API — CreateThreadpoolWork দিয়ে থ্রেড তৈরি না করে কনকারেন্সি
নেটিভ কোডে চারদিকে CreateThread ডাকছেন? এই নিবন্ধ Vista-তে নতুন করে সাজানো Win32 থ্রেড পুল API ব্যাখ্যা করে — work, timer, wait ও io চারট...
ঘুম থেকে জাগলে ভাঙে যে অ্যাপ — Windows পাওয়ার ইভেন্টের কাজ ও যে ব্যবসায়িক অ্যাপ তা সহ্য করে
ল্যাপটপ খুললেন আর ব্যবসায়িক অ্যাপের সংযোগ মৃত — কারণ ঘুম ধরে না নেওয়া ডিজাইন। এই নিবন্ধ WM_POWERBROADCAST নোটিফিকেশন প্রবাহ, Modern Sta...
DllMain ও লোডার লক — DLL ইনিশিয়ালাইজেশনে "কিছুই করবেন না" বলা হয় তার আসল কারণ
DllMain থেকে LoadLibrary ডাকা বা অন্য থ্রেডের সাথে সিঙ্ক্রোনাইজ করা যায় না কেন। প্রাথমিক উৎস থেকে এই নিবন্ধ ব্যাখ্যা করে লোডার লক প্রতিট...
"Not Responding" আসলে কী — Windows কীভাবে অ্যাপ হ্যাং ধরে, আর যে ডিজাইন হ্যাং করে না
Windows-এর "Not Responding" এমন যন্ত্র যেখানে OS বিচার করে উইন্ডো ৫ সেকেন্ড মেসেজ তোলেনি আর তাকে ঘোস্ট উইন্ডো দিয়ে বদলায়। এই নিবন্ধ সেই...
সম্পর্কিত বিষয়
এই পৃষ্ঠাগুলো বিষয়টিকে সেবা ও সিদ্ধান্তের বৃহত্তর প্রেক্ষাপটে স্থাপন করে।
Windows-এর প্রযুক্তিগত বিষয়
Windows ডেভেলপমেন্ট, বাগ তদন্ত ও বিদ্যমান সম্পদ ব্যবহারের প্রবেশদ্বার।
এই বিষয়ের সাথে সম্পর্কিত সেবা
নিবন্ধটি নিচের সেবাগুলোর সাথে সরাসরি সম্পর্কিত।
Windows অ্যাপ ডেভেলপমেন্ট
ব্যবসায়িক অ্যাপ, ডিভাইস ইন্টিগ্রেশন ও যোগাযোগ টুল, চাহিদা থেকে ডেভেলপমেন্ট পর্যন্ত।
প্রায়শ জিজ্ঞাসিত প্রশ্ন
এই নিবন্ধের বিষয়ে পরামর্শে প্রায়ই আসা প্রশ্ন।
- CreateThread ব্যবহার করব নাকি _beginthreadex?
- যে থ্রেড C রানটাইম লাইব্রেরি (CRT) ফাংশন ডাকে তার জন্য _beginthreadex ব্যবহার করুন। _beginthreadex থ্রেড শুরু করার আগে CRT-এর প্রতি-থ্রেড অভ্যন্তরীণ ডেটা আরম্ভ করে। সরকারি নথি স্পষ্ট বলে যে CreateThread দিয়ে তৈরি থ্রেড CRT ফাংশন ডাকলে কম মেমরিতে CRT প্রক্রিয়া শেষ করতে পারে। বাস্তবে C অ্যাপের থ্রেড প্রায় সবসময় কোথাও CRT ফাংশন (printf, malloc, strtok ইত্যাদি) ডাকে, তাই নিয়ম «সবসময় _beginthreadex» মনে রাখতে ক্ষতি নেই। _beginthread (ex ছাড়া) এড়িয়ে চলুন — তাতে ফাঁদ আছে যে তৈরি থ্রেড তাড়াতাড়ি শেষ হলে ফেরত হ্যান্ডেল অবৈধ হতে পারে, তাই _beginthreadex বেছে নিন, যার হ্যান্ডেল সিঙ্ক্রোনাইজেশন API-তে দেওয়া যায়।
- আমি কি TerminateThread দিয়ে থ্রেড থামাতে পারি না?
- না, করা উচিত নয়। TerminateThread লক্ষ্য থ্রেডকে কোনো ইউজার-মোড কোড চালাতে না দিয়ে মুছে দেয়, তাই সেই থ্রেড ক্রিটিক্যাল সেকশন ধরে থাকলে তা কখনো ছাড়ে না, হিপ থেকে মেমরি বরাদ্দ করলে হিপ লক ধরা থাকে, আর কোনো DLL-এর গ্লোবাল অবস্থা বদলাচ্ছে তো সেই অবস্থা নষ্ট হয়। সরকারি নথি স্পষ্ট বলে এটি «একটি বিপজ্জনক ফাংশন যা কেবল সবচেয়ে চরম ক্ষেত্রে ব্যবহার করা উচিত», আর কোড বিশ্লেষণও একে সতর্কতা C6258 দেয়। সঠিক থামানো সহযোগী শাটডাউন: স্টপ ইভেন্ট তৈরি করুন, প্রতি থ্রেড WaitForSingleObject / WaitForMultipleObjects দিয়ে সেই ইভেন্ট দেখুক, আর প্রতি থ্রেড নিজে পরিষ্কার করে নিজে শেষ হোক।
- প্রক্রিয়ার ভিতরে বর্জনের জন্য Mutex ব্যবহার করছিলাম। সমস্যা কী?
- চলে, কিন্তু কর্মক্ষমতার বড় দাম দিতে হয়। Win32 Mutex সবসময় কার্নেল অবজেক্ট, তাই প্রতি অর্জন ও মুক্তি কার্নেল মোডে স্থানান্তর ঘটায়। এক প্রক্রিয়ার ভিতরে বর্জনের জন্য SRW লক বা CRITICAL_SECTION — যেগুলো ইউজার মোডে থাকে এবং কেবল প্রতিযোগিতায় কার্নেল অপেক্ষায় পড়ে — অনেক দ্রুত, আর সরকারি নথি প্রক্রিয়া-অভ্যন্তর সিঙ্ক্রোনাইজেশনে Mutex-কে স্পষ্ট «সাধারণ ভুল» বলে। Mutex তখন কাজে আসে যখন নামযুক্ত অবজেক্ট হিসেবে প্রক্রিয়া পেরিয়ে বর্জন লাগে, বা WaitForMultipleObjects দিয়ে অন্য কার্নেল অবজেক্টের সাথে অপেক্ষা করতে চান।
- Windows-এ C11-এর threads.h ও stdatomic.h ব্যবহার করা যায়?
- MSVC-তে C11 থ্রেড (threads.h) Visual Studio 2022 17.8 থেকে সমর্থিত (/std:c11 ও মিল Windows SDK লাগে)। অন্যদিকে stdatomic.h এখনও পরীক্ষামূলক, /experimental:c11atomics অপশন চায় (আগস্ট 2026-এর সরকারি সামঞ্জস্য সারণি অনুসারে)। পোর্টেবিলিটি সর্বোচ্চ অগ্রাধিকার হলে এটি কার্যকর বিকল্প, কিন্তু কেবল-Windows কোডবেসের জন্য Win32 API (_beginthreadex, SRW লক, কন্ডিশন ভেরিয়েবল, Interlocked ফাংশন) লেখা তথ্যের পরিমাণ ও ট্র্যাক রেকর্ডের হিসাবে বাস্তব পছন্দ।
- volatile যোগ করলে কি শেয়ার করা ফ্ল্যাগ নিরাপদ হয়?
- না। C-এর volatile কেবল কম্পাইলার অপটিমাইজেশন যেমন রেজিস্টারে মান ক্যাশ দমন করে — অপারেশনের পরমাণুতাও গ্যারান্টি দেয় না, প্রসেসরের মধ্যে মেমরি ক্রমও না। সঠিকভাবে সারিবদ্ধ ৩২-বিট ভেরিয়েবলের সাধারণ পড়া বা লেখা Windows-এ নিজেই পরমাণু, কিন্তু «পড়ো, যোগ করো ও ফিরিয়ে লেখো» আলাদা ধাপে ভাঙে, আর আশপাশের মেমরি অপারেশনের সাপেক্ষে ক্রমেরও গ্যারান্টি নেই। শেয়ার করা কাউন্টার বা ফ্ল্যাগ আপডেটে Interlocked পরিবার ব্যবহার করুন। বেশিরভাগ Interlocked ফাংশন পূর্ণ মেমরি ব্যারিয়ার বহন করে, তাই ক্রমের গ্যারান্টিও একসাথে পাওয়া যায়। কয়েকটি ভেরিয়েবল একসাথে রক্ষা করতে SRW লক বা CRITICAL_SECTION নিন।