ThreadPool Whit Windows API in C & C++
بیاید بحثمون رو با یک مثال شروع کنیم : نرم افزار وب سرور
یک برنامه وب سرور، به یک پورت مشخصی (https:443) درحال گوش دادن (listing) است.
هر کلاینتی که به این وب سرور وصل میشه، یک درخواستی را ارسال میکند. ( درخواست دریافت صفحه x.html ) ، امکان دارد در هر لحظه صد ها یا هزاران درخواست به وب سرور ارسال شود .
+ وب سرور در جواب چه کاری انجام میدهد ؟
وب سرور در قبال این درخواست ها، یک کدی را اجرا میکند که وظیفه کد، این است که از روی هارد یک فایلی را به کلاینت روی شبکه ارسال کند . مثلا برای یک کلاینتی فایل x را ارسال میکند ، برای کلاینت دیگری فایل y و....
چیزی که مشخص است ، وب سرور یک کد یکسانی را برای همه کلاینت ها اجرا میکند. پس در نتیجه سرور باید به همه ی کلاینت ها پاسخگو باشد . از این حیث 2 روش وجود دارد :
1 . سرور به ازای هر کلاینت (درخواست) ، یک فرایند جدید ایجاد کند . که این کار ، فضا و زمان زیادی صرف میکند (تخصیص PID ، حافظه ، ساختن PCB ،stack,data, heap و کپی کردن image core فرایند والد در فرایند فرزند) .
2 . ایجاد یک نخ جدید به ازای هر درخواست. چون نخ ها از حافظه ، منابع و کد فرایند استفاده میکند و نیازی به ایجاد حافظه و کپی کردن Address Space نیست . پس این روش سریع است و حافظه کمتری اشغال میکند .
+ حال که از روش نخ استفاده کردیم . دقیقا چه اتفاقی میافتد ؟
یک برنامه Single Thread ، درحال اجرای تابع main(Primary Thread) است که منتظر درخواست های کلاینت ها است . یک تابع x داریم که وظیفه جواب دادن به درخواست های کلاینت هارا دارد . حال وقتی درخواستی به برنامه میاد . تابع main خودش پاسخگوی آن نمیشه و بجاش تابع x را به صورت نخ فراخوانی میکند. (در ادامه بیشتر توضیح میدم) و تابع main منتظر درخواست های کلاینت های بعدی است .
حال دو نخ در سیستم داریم که میتوانند به صورت موازی یا همروند اجرا شوند .
Thread Pool
گفتیم که وب سرور به ازای هر درخواست یک نخ ایجاد میکند. درسته که ایجاد و Context Switch بین نخ ها سریع تراست وحافظه کمتری اشغال میکنند. ( فقط نیاز به ساخت TCB , Stack است) ، اما در وب سرور های بزرگ در هر لحظه هزاران درخواست وجود دارد . و ایجاد تعداد زیادی نخ زمانبر و حافظه بیشتری اشغال میکند . و باعث به وجود آمدن مشکلات زیر میشود :
1 . ایجاد تعداد زیاد نخ زمانبر است.
2 . ایجاد تعداد زیادی نخ حافظه ی بیشتری اشغال میکند.
3 . Response Time سایت افزایش میابد. ( اگر 10000 تا نخ ایجاد بشه و در صف Ready قرار بگیرد . تا کوانتوم بین اینها بچرخد زمان زیادی صرف میشود)
+ راهکار چیست : ایجاد استخر نخ (Thread Pool)
ابتدا براساس معیارهای زیر باید تعیین شود که چه تعداد نخ در برنامه وب سرور باشد، بهینه است.
معیارها :
1 . ظرفیت حافظه اصلی چقدر است.
2 . چند هسته در پردازنده وجود دارد.
3 . برنامه CPU-Bound است یا I/O-Bound
4 . چند درخواست همزمان میتواند به وب سرور ارسال شود
مثال :
مشخصات سیستم : 12 گیگ رم – یک پردازنده 6 هسته – 1000 تا درخواست همزمان - I/O-Bound
برفرض مثال :2000 = x میتوانیم نخ داشته باشیم .
موقع ای که برنامه وب سرور برای اولین بار شروع به اجرا میکند ،x تا نخ ایجاد میکند و در صف قرار میدهد . حال درخواست هایی که از سمت کلاینت ها میاد را در صف وظایف قرار میگیرند . هر نخی که یک درخواست را از صف وظایف برداشت و انجام داد ، دوباره صف را نگاه میکند ، اگر درخواستی وجود داشت برمیدارد و انجام میدهد و اگر وجود نداشت ( نخ پایان نمیابد (Termint نمیشود)) ، دوباره میره توصف وظایف و به همین منوال این کار ادامه میابد . به عبارتی ، برنامه تا x درخواست را بصورت همزمان میتواند جواب دهد .
چگونه مشکلات بالا حل شد ؟
مشکل 1 . چون نخ ها در شروع برنامه ایجاد میشوند و آماده اجرا هستند . نیازی به ایجاد کردن نخ نیست .
مشکل 2 . در مورد اول که به ازای هردرخواست یک نخ ایجاد میکردیم ، اگر 10000 تا درخواست میامد ، باز آن تعداد نخ ایجاد میشد و حافظه (هرچند کوچیک و سبک) براش ساخته میشد . و امکان داشت حافظه پر بشه و سیستم به حالت انفجاری برسد . اما درمورد دومی (استخر نخ) ما همان اولش نخ هارا براساس معیار هامون ایجاد میکنیم، و نخ اضافی ایجاد نمیشه .
مشکل 3 . ما همان اولش نخ هارا براساس معیار هامون ایجاد میکنیم و تعداد هسته در یک پردازنده را در نظر میگیریم . پس Response Time کاهش میابد . (سرعت پاسخگویی سرور بالا میرود.)
به طورخلاصه و نگاهی فنی تر :
وقتی وب سرور اجرا میشود ، یک فرایند تک نخی (Single Thread) است. و x تا نخ غیرفعال در صف وجود دارد ، حال وقتی بخواهیم یک سری وظایف را اجرا کنیم، از صف یک نخ را فعال میکنیم و وظیفه (تابع ای که باید اجرا بشه) را به آن محول میکنیم، که در این حالت ما یک فرایند چند نخی یا (Multi Thread) داریم . وقتی نخ کارش را انجام داد . آن را از بین نمیبریم (منابع و TCB, Stackرا از بین نمیبریم )، بلکه غیرفعال میکنیم و میزاریمش تو صف نخ ها .* در این روش ایجاد و حذف نخ سریع تر است .
2 نوع استخر نخ داریم :
1 . استخر نخ cache مانند : مثلا در اول minimum نخی(50 تا نخ) که نیاز داریم، را ایجاد میکنیم ،اگر درخواست ها زیاد شد ، 50 نخ دیگر ایجاد و به این صف اضافه میکنیم ، اما اگر درخواست کم شد تعدادی از این نخ هارا از بین میبریم. (termint thread)
2 . استخر نخ تعداد ثابت : همان اول کار n نخ ایجاد میکنیم.
Thread Pool Win32 API whit C & C++
معماری پیاده سازی Thread Library در ویندوز به صورت One to One است . هر فرایندی در این سیستم عامل یک استخرنخ پیشفرض دارد .
مفاهیم استخرنخ در ویندوز :
1 . Application Code : همان کد های برنامه است که در اثر اجرا ، وظایف ایجاد میشوند . و به صف وظایف (Work Queue) ارسال میشوند .
2 . Worker Threads (نخ های کاری) : همان استخرنخ است که نخ ها در آن وجود دارند . و وظایف را اجرا میکنند .
3 . Worker Factor | Thread Manger : وطیفه زمانبندی ، تخصیص نخ ها به وظایف ، مدیریت منابع را برعهده دارند .
4 .Thread Waiter : نخ هایی هستند که منتظر یک Event - سیگنال از سیستم عامل و یا پاسخ از سرور هستند .
5 . Thread Timer : نخ هایی هستند که وظایفی را بصورت دوره ای انجام میدهند . (مثلا یک تابع ای را هر 5 ثانیه اجرا میکند )
· Cleanup Group & I/O Threads درآینده اضافه خواهد شد .
نحوه ایجاد یک Task (تابع) | وظیفه ، برای اجرای Worker Threads
My_func : نام تابع موردنظر
Parametr: ورودی های تابع
توجه : نوع داداه ها در وورودی تابع و خروجی ، باید از استاندارد بالا تبعیت کند .
مراحل ایجاد Work Thread در استخرنخ پیشفرض فرایند
مرحله 1 : ساختن یک وظیفه کاری (Work) برای ارسال به صف وظایف (Task Queue) که با تابع CreateThreadPool() میتوان ایجاد کرد . ورودی های این تابع به ترتیب :
1. نام تابع مورد نظر برای اجرا (در بالا نحوه ایجادش رو توضیح دادم)
2. ورودی تابع
3.محیط اجرای این وظیفه کاری (جلوتردرباره این توضیح خواهم داد)
+ خروجی این تابع متغیری از نوع Work است که از طریق آن به Task مورد نظر دسترسی داریم .
نحوه استفاده :
مرحله 2 : اضافه کردن این وظیفه کاری یا ارسال آن به استخرنخ برای اجرا. با تابع SubmitThreadPoolWork :
فقط یک ورودی دارد : متغیری از نوع Work ، که در مرحله 1 ایجادش کردیم .+ حال آن تابع (my_func) رفت برای اجرا .
مرحله 3 : منتظر ماندن برای اتمام کار که 2 روش وجود دارد :
1 . تابع Sleep()
2 . تابع WaitForThreadpoolWorkCallbacks : 2 آرگومان ورودی میگیرد .
آرگومان اول : منتظر اتمام کدام وظیفه کاری بمانم ؟ (منتظر پایان یافتن work باش).
آرگومان دوم : فرض کنید چندین وظیفه (تابع) همزمان در حال اجراست . این آرگومان اگر True باشد ، به محض اینکه متغیر work تمام شد ، بقیه وظایف درحال اجرا را هم کنسل میکند. اما اگر False باشد ، منتظر پایان یافتن همه میماند .
+ اگر این مرحله (3) وجود نداشته باشد ، تابع main نیز همروند یا موازی اجرا میشود . و اگر به پایان خود رسید ، همه نخ ها (حتی در حالت اجرا) از بین میروند .
مرحله 4 : آزاد کردن منابع در آخر برنامه با تابع CloseThreadpoolWork
فقط یک ورودی دارد : متغیر work
یک کد کامل با زبان C از این بخش :
مراحل ایجاد چندین وظیفه | نخ کار (Work Thread) در استخرنخ پیشفرض
الگوریتم و نحوه کار مانند (ایجاد Work Thread) است . فقط بجای ایجاد یک work ، چندین work را ایجاد و به استخر نخ ارسال میکنیم. به برنامه زیر دقت کنید :
بجای استفاده تکی از متغیر work ، یک لیستی از work ها ایجاد کردیم و در حلقه تکرار وظایف را یکی یکی ایجاد و به استخرنخ ارسال کردیم. و در پایان ، داخل یک حلقه تکرار ، منابع را آزاد کردیم .
+میتوانیم در یک فرایند چندین استخرنخ مستقل داشته باشیم . که هر کدام صف وظایف و نخ های خودشون رو داشته باشند ، و آنها را مدیریت کنند.
ایجاد استخرنخ (ThreadPool) مستقل در فرایند
مرحله 1 : ایجاد استخرنخ جدید با تابع CreateThreadpool انجام میشود .
خروجی این تابع متغیری از نوع PTP_POOL است . که شناسه ای برای دسترسی به استخرنخ را برمیگرداند.
ورودی تابع، یک مقدار رزرو شده است ، و ورودی ندارد .
مرحله 2 : بعد از ساخت استخرنخ باید مشخص کنیم Minimum & Maximum نخی که در این استخر قرار بگیرد چقدر است . که این کار با توابع :
SetThreadpoolThreadMinimum() : دو آرگومان میگیرد . 1: شناسه استخرنخ (th_pol) 2 . کمترین نخ موجود در استخرنخ.
SetThreadpoolThreadMaximum() : دو آرگومان میگیرد . 1: شناسه استخرنخ (th_pol) 2 . بیشترین نخ موجود در استخرنخ.
اگر عملیات ، موفق آمیز بود . تابع مقدار True برمیگرداند .
مرحله 3 : ایجاد محیط اجرای توابع بازگشتی ، در استخرنخ با متغیری از نوع TP_CALLBACK_ENVIRON
مرحله 4 : بعد از اینکه محیط اجرا را تعریف کردید(مرحله 3) ، باید آن محیط اجرا را با تابع (InitializeThreadpoolEnvironment) مقداردهی کنید. این کار به شما اجازه میدهد تنظیماتی مثل اولویت، یا Thread Pool خاص را اعمال کنید.
این تابع آدرس متغیری از نوع TP_CALLBACK_ENVIRON به عنوان ورودی میگیرد .
مرحله 5 : از جایی که فرایند یک استخرنخ پیشفرض دارد، و الان یک استخرنخ جدید ایجاد کردیم، باید آن را به سیستم معرفی کرده و به عنوان استخرنخ فرایند تنظیم کنیم با تابع ()SetThreadpoolCallbackPool میشود اینکار را انجام داد .
دو آرگومان میگیرد : 1 . محیط اجرای توابع 2. شناسه استخرنخ جدید
مرحله 6 : آزاد کردن منابع در پایان برنامه
1 . تابع () DestroyThreadpoolEnvironment برای آزاد کردن منابع (محیط اجرا ) به کار میرود . این تابع یک ورودی دارد ، که آدرس متغیر(penc) محیط اجرا است.
2 . تابع CloseThreadpool() نیز برای آزاد کردن منابع استخرنخ است . ووردی تابع شناسه استخرنخ است .
یک کد کامل از ایجاد استرنخ جدید و از بین بردن آن با یک وظیفه کاری :
تا الان وظایف (Tasks) برنامه را ، نخ های کاری (Thread Worker) انجام میداند .
مثال : برنامه دانلود منیجر را در نظر بگیرید که درحال دانلود فایلی است . وسط های دانلود یهو ارتباط با سرور قطع میشه . دانلود منیجر به تعداد مشخصی ، مثلا 10 بار تلاش میکند تا دوباره با سرور ارتباط برقرار کند . برای پیاده سازی این سناریو ، مکانیزم های مختلفی وجود دارد که یکی از آنها ThreadpoolTimer
است .
مراحل ایجاد Thread Timer با Win32 API در زبان C & C++
نخ های نوع ThreadpoolTimer ، وظیفه (Task) مورد نظر را بصورت دوره ای (مانند حلقه ها در برنامه نویسی (for)) ، یا یکبار انجام میدهند . طریقه استفاده به این شکل است :
مرحله اول : یک محیط اجرا برای توابع برگشتی تعریف میکنیم . (مانند مرحله 3 و 4 بخش : تعریف استخرنخ جدید | مستقل)
مرحله 2 : ایجاد آبجکتThread Timer با تابع ()CreateThreadpoolTimer :
ورودی ها :
1. تابع مورد نظر برای اجرا
2. ورودی های تابع
3. آدرس متغیر محیط اجرا (tce)
خروجی : شناسه ای برای دسترسی به شی
مرحله 3 : اجرا کردن تایمر با تابع ()SetThreadpoolTimer
ورودی ها : (PTP_TIMER pti, PFILETIME pftDueTime, DWORD msPeriod, DWORD msWindowLength)
PTP_TIMER pti : شناسه همان تایمری که ایجاد کردیم
DWORD msPeriod : اگر تایمر به صورت دوره ای نباشد مقدار این ارگومان 0 خواهد بود . اما اگر دوره ای باشد، این مقدار باید بزرگتر از صفر باشد . مثال : مقدار این ارگومان اگر 2000 باشد ، به معنی آن است که هر 2 ثانیه یکبار تابع را اجرا کن .
DWORD msWindowLength : حداکثر زمان تاخیری که در اجرای تابع وجود داشته باشد . مثال : اگر تایمر تنظیم شود که در زمان مشخصی (مثلا فردا در ساعت 2 بعد از ظهر) اجرا شود ، اگر این ارگومان 3000 باشد ، به معنی آن است که سیستم موظف است بین ساعت 2 تا 2 و 3 ثانیه تایمر را اجرا کند ، که این امر باعث میشود در مصرف انرژی صرفه جویی شود .
PFILETIME pftDueTime : این آرگومان زمان شروع تایمر را مشخص میکند ، که با متغیری از نوع FILETIME تعریف میشوند . مقدار این آرگومان اگر 0 باشد ، تایمر از الان شروع به اجرا میکند .
2 نوع زمان داریم در سیستم :
1. نسبی : زمانی که از الان شروع (بعد از اجرای برنامه )میشود ، واحدش نانو ثانیه است و زمان را بصورت منفی وارد میکنند .
مثال : اگر 50000000 – وارد کنیم ، 5 ثانیه بعد از اجرای برنامه ، تایمر شروع به اجرا کند .
توجه : مقدار زمان نسبی نمیتواند مثبت باشد ، و چون زمان بر حسب نانو ثانیه است : 10000000 = 1 ثانیه
2 . زمان مطلق : زمانی است که در تقویم وجود دارد . مانند آلارم گذاشتن ، مثال : در تاریخ 6/1/2025 ساعت 10 و 30 دقیقه شروع به اجرای تابع کن .
توجه : زمان نسبی در حالت خواب سیستم (Hibernate) در نظر گرفته نمیشود. مثال : فرض کنید 60- ثانیه را برای تایمر ست کردید ، بعد از گذشت 20ثانیه سیستم به حالت خواب میرود ، همینجا تایمر متوقف شده و هروقت سیستم شروع به اجرا کند ، 40 ثانیه باقی مانده طی میشود تا تایمر فعال شود . اما در زمان مطلق خواب سیستم لحاظ میشود و تایمر پس از بیدار شدن سیستم فعال میشود .
مرحله 4 : تعریف متغیری از نوع FILETIME برای پیکربندی زمان اجرا .
نحوه تعریف کردن زمان مطلق برای FILETIME
1 . با استفاده از تعریف متغیری از نوع SYSTEMTIME زمان میلادی به زبان انسان را دریافت میکنیم و بعد میتوانیم آن متغیر را با زمان دلخواهمون مقداردهی کنیم . اگر قصد مقداردهی یکی از پارامتر هارا نداشتید . میتوانید صفر قرار بدهید .
نحوه مقداردهی بقیه پارامتر ها :
نکته : اگر قصد دارید تایمری را برای ماه آینده ، فردا یا چند ساعت آینده ، ست کنید ، نیازی نیست همه پارامتر ها را بصورت دستی مقداردهی کنید ، با تابع GetSystemTime میتوانید زمان حال را دریافت کرده و در متغیری که بالا تعریف کردیم ذخیره کنید ، بعد پارامتر زمانی را که نیاز دارید را تغییر بدهید .
ورودی تابع GetSystemTime : آدرس متغیری از نوع SYSTEMTIME
مثال : قصد داریم تایمر را برای امروز ، همین ساعت ، اما دقیقه متفاوت ست کنیم . تاریخ امروز (2025/8/10 11:10:32) ، تاریخی که میخایم ست کنیم : (2025/8/10 11:53:00)
این کار را انجام میدهیم :
چون فقط دقیقه و ثانیه قرار بود عوض بشوند ، این دو پارامتر را بعد از گرفتن زمان سیستم و قرار دادن آن در متغیر st ، ثانیه و دقیقه را در آن متغیر ، تغییر دادیم . (2025/8/10 11:53:00)
2. تبدیل تاریخ انسانی (2025/8/10 11:53:00) به تاریخ کامپیوتری با تابع SystemTimeToFileTime
ورودی ها : 1. آدرس SYSTEMTIME 2. آدرس FILETIME
خلاصه زمان مطلق : یک متغیر از نوع SYSTEMTIME برای دریافت زمان انسانی تعریف کردیم ، با تابع SystemTimeToFileTime آن را به زمان سیستم تبدیل کردیم ، و در متغیری از نوع FILETIME ذخیره کردیم .
مرحله 5 : اجرایی کردن مرحله 3 بعد از تکمیل پارامتر ها .
ورودی ها به ترتیب :
1. شی تایمری که ایجاد کردیم (مرحله 2)
2. زمان شروع تایمر که از نوع FILETIME است ، (مرحله 4)
3 . چون مقدارش 0 است ، یعنی تابع فقط یکبار اجرا میشود . اگر 2000 بود ، هر 2 ثانیه یکبار بصورت دوره ای اجرا میشد .
4 . حداکثر زمان تاخیر برای اجرای تابع
نمونه کامل از این کد :
تا الان ما ، زمان مطلق را یاد گرفتیم ، بهتره نگاهی به زمان نسبی هم بیاندازیم .
نحوه تعریف کردن زمان نسبی برای FILETIME
1. تعریف متغیری از نوع FILETIME برای پیکربندی زمان اجرا .
2 . تعریف متغیری از نوع ULARGE_INTEGER برای ذخیره زمان حال (زمان انسانی)
3 . ذخیره کردن زمان اجرا در پارامتر ul.QuadPart ، که باید TypeCast شود به (ULONGLONG) ، و منفی باشد (بنا به دلایلی که در بالا گفتیم).
توجه : زمان بر حسب نانو ثانیه است .
4 . تبدیل این زمان انسانی به زمان سیستم :
5 . اجرا کردن تایمر با تابع SetThreadpoolTimer بعد از تکمیل پارامتر هاش :
ورودی ها به ترتیب :
1. شی تایمری که ایجاد کردیم (مرحله 2)
2. زمان شروع تایمر که از نوع FILETIME است ، و بصورت نسبی مقدار دهی کردیم .
3 . چون مقدارش 2000 است ، هر 2 ثانیه یکبار بصورت دوره ای اجرا میشود .
4 . حداکثر زمان تاخیر برای اجرای تابع ، که 0 هست . و به معنی بی درنگ اجرا شدن است .
نمونه ای از کد کامل :
مفهوم تابع WaitForThreadpoolTimerCallbacks ، منتظر ماندن برای تمام شدن تابع درحال اجرا .
آرگومان اول : منتظر اتمام کدام تایمر بمانم ؟ (منتظر پایان یافتن timer باش).
آرگومان دوم : فرض کنید چندین تایمر (تابع) همزمان در حال اجراست . این آرگومان اگر True باشد ، به محض اینکه متغیر timer ما اجراش تمام شد ، بقیه تابع های درحال اجرا را هم کنسل میکند. اما اگر False باشد ، منتظر پایان یافتن همه میماند ، حتی اگر تابع ما اجراش تمام شده باشد .
مفهوم تابع CloseThreadpoolTimer ، آزاد کردن منابع سیستم است .
نکته : معمولا توابعی از نوعWaitForThreadpool….. (timer or work or wait) واقعا منتظر پایان اجرای تابع نمی مانند (یعنی تابع main بلاک نمیشود و به اجراش ادامه میدهد ) . و معمولا از این توابع ، هنگام آزاد کردن منابع استفاده میکنند ، تا مطمن باشند توابع (نخ) در حال اجرا ، کارشون به اتمام رسیده . اما اگر تابع main به اجراش ادامه بده ، امکان دارد برنامه به آخر برسد و سیستم کال exit را فراخوانی کند ، که در این حالت همه منابع از فرایند گرفته میشود و تمام نخ های در حال اجرا kill میشوند . پس اگر نیاز داشتیم ، تا تمام نشدن تابع مورد نظر برنامه بلاک شود و یا منتظر بماند تا آن تابع کارش را انجام دهد ، از Event استفاده میکنیم .
نحوه تعریف کردن Event
به معنی رویداد است . بیشتر برای همگام سازی بین نخ ها استفاده میشود. مثال :فرض کنید یک نخی به دیتای یک نخ دیگر نیاز دارد و منتظر پایان یافتن آن است ، هروقت کار اون نخ تمام شد ، میاد و از event برای مطلع کردن نخ در حال انتظار استفاده میکند .
مرحله 1 : اول Threadpool work|timer|wait را ایجاد و ارسال میکنیم به استخرنخ برای اجرا .
مرحله 2 : با تابع CreateEventW یک event ایجاد میکنیم .
ورودی ها :
1. تنظیمات امنیتی ، که معمولا مقدار NULL به معنی استفاده تنظیمات امنیتی پیشفرض است .
2. اگر True باشد ، به معنی ریست دستی است . اگر false باشد ، بعد از آزاد شدن نخ بصورت خودکار ریست میشود .
3 . وضعیت اولیه رویداد . موقع ساخت فعال باشد (True)، یا غیرفعال باشد (False).
4 . اسم دلخواه برای event ،که اگر NULL باشد ، بدون نام خواهد بود .
خروجی : شناسه ای از نوع Handle برمیگداند . برای دسترسی به وضعیت event
توجه : متغیر hEvent که از نوع HANDLE است ، باید بصورت Global تعریف شود . تا در همه توابع بهش دسترسی داشته باشیم . مانند کد زیر :
مرحله 3 : سپس با تابع WaitForSingleObject منتظر فعال شدن آن hEvent می مانیم .
ورودی ها : 1. handle
2 . مدت زمان منتظر ماندن (به میلی ثانیه )، (INFINITE به معنای تا بینهایت منتظر ماندن است) . اگر شما 10000 وارد کردید ، به معنی آن است که 10 ثانیه منتظر بمون .
+از این تابع برای متتظر ماندن پایان فرایند فرزند نیز استفاده میشود .
مرحله 4 : فعال سازی event با تابع SetEvent انجام میشود . مثال : فرض کنید تابع (نخ) ، کارش را انجام داده و به پایان رسیده ، و نخ دیگری منتظر این تابع مانده است ، با استفاده از SetEvent به تابع در حال انتظار، اطلاع میدهد که کارش را به اتمام رسونده .
ورودی : یک Handle میگیرد .
به محض اینکه این تابع (setEvent)فراخوانی شد ، تابع در حال انتظار (main) از بلاک خارج میشود . (از حالت انتظار خارج میشود)
در نهایت با تابع ResetEvent ، Handle (hEvent) غیرفعال میشود ، برای استفاده دوباره .
+تابع WaitForSingleObject تا زمانی که hEvent غیرفعال است ، منتظر میماند . وقتی که فعال شد (با تابع SetEvent ) ، انگار جوابش را گرفته و دیگر منتظر نمی ماند .
+یک تابع دیگر برای منتظر ماندن برای تعدادی handle ها وجود دارد، به اسم WaitForMultipleObject که به این شکل عمل میکند :
ابتدا تعداد Handle هارا میگیرد ، بعد لیست Hnadle هارا میگیرد ، پارامتر سوم اگر True باشد ، برای همه منتظر میماند ، درغیراینصورت منتظر اولین Handle که تمام شود ، میماند . پارامتر آخر نیز مدت زمان انتظار است.
کد کامل Event:
فرض کنید ، برنامه دانلود منیجر در حال دانلود فایلی است که یهو ارتباط قطع میشود ، دانلود منیجر به تعداد مشخصی(مثلا 10 بار)،سعی میکند با سرور ارتباط بگیرد،که میتوانیم این نخ را از نوع ThreadPoolTimer
تعریف کنیم تا کد مشخصی را بصورت دوره ای اجرا کند . حال دانلود منیجر میخواهد ، وقتی به سرور متصل شد ، یک تابع مشخصی را اجرا کند .(کار این تابع ، دریافت بقیه فایل مورد نظر است) پس ما نیاز به نوع نخی داریم که ، منتظر یک رخدادی بماند (ارتباط دانلود منیجر با سرور ) ، و هنگامی که این اتفاق افتاد ،بیاد و تابع مذکور را اجرا کند .
نحوه تعریف کردن Thread Wait
تا زمانی که Event رخ دهد یا Handle فعال شود ، این تابع منتظر میماند ، و هروقت رخ داد یک تابع مشخصی را اجرا میکند .
مرحله (1). نحوه تعریف تابع برای اجرا توسط Thread Wait :
آرگومان (wrsult) TP_WAIT_RESULT نوع داده ای است که مشخص میکند ، آیا اجرای تابع توسط Thread Wait در زمان مقرر شده انجام یافته یا timeout رخ داده است .
مرحله (2). تعریف محیط اجرا برای تابع های برگشتی
مرحله (3). ایجاد نخ انتظار با تابع CreateThreadpoolWait :
ورودی ها :
1. تابع ای که بعد از رخداد اجرا میشود.
2. ورودی آن تابع
3. محیط اجرا
خروجی : شناسه ای برای دسترسی به (thread wait)
مرحله (4). ایجاد Event ، برای شبیه سازی رخداد
مرحله (5). ارسال نخ انتظار (thread wait) به استخرنخ ، جهت اجرا با تابع SetThreadpoolWait
ورودی ها :
1 . شناسه thread wait (pwit)
2 . منتظر فعال شدن کدام handle باشم؟ (hevent که در بالا ایجاد کردیم)
3 . چقدر منتظر رویداد بمانم ؟:
این ورودی یک زمان از نوع FILETIME (در بالا توضیح دادیم) میگیرد . (مثلا 10 ثانیه) ، اگر در این 10 ثانیه Handle فعال شد، wrsult با WAIT_OBJECT_O مقداردهی میشود ، در غیر اینصورت Timeout رخ میدهد و wrsult با WAIT_TIMEOUT مقداردهی میشود .
+نحوه ایجاد FILETIME ، که 2 ثانیه منتظر رویداد میماند ، در غیر اینصورت TimeOut برمیگرداند.
مرحله (6). فعال سازی Handle ، به منظور شبیه سازی رویداد با تابع SetEvent :
+قبل از این تابع ، میتوانیم با تابع Sleep اجرای برنامه را شبیه سازی کنیم .
+این تابع ، نخ اصلی برنامه را (Primary Thread) ، 40 ثانیه بلاک میکند . واحدش به میلی ثانیه است .
مرحله (7) . آزادسازی منابع :
کد کامل :
مرسی که تا اینجای مقاله با من بودید ، امیدوارم مطالب به دردتون خورده باشند ، پیشنهاد میکنم تمام مطالب را با مثال های واقعی (پیاده سازی صفحه چت با کلاینت های بیشتر یا شبیه سازی دریافت آبجکت های یک صفحه ی html بصورت موازی ) تمرین کنید .
نظرات کاربران (0)