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   تا نخ ایجاد میکند و در صف قرار میدهد . حال درخواست هایی که از سمت کلاینت ها میاد را در صف وظایف قرار میگیرند . هر نخی که یک درخواست را از صف وظایف برداشت و انجام داد ، دوباره صف را نگاه میکند ، اگر درخواستی وجود داشت برمیدارد و انجام میدهد و اگر وجود نداشت ( نخ پایان نمیابد (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  درآینده اضافه خواهد شد . 

Worker Factor | Thread Pool Manager

 نحوه ایجاد یک Task (تابع) |  وظیفه ، برای اجرای Worker Threads 

  void CALLBACK My_func(PTP_CALLBACK_INSTANCE instnc , PVOID parametr , PTP_WORK)

My_func  : نام تابع موردنظر

 Parametr: ورودی های تابع 

توجه : نوع داداه ها در وورودی تابع و خروجی ، باید از استاندارد بالا تبعیت کند . 

  مراحل ایجاد Work Thread  در استخرنخ پیشفرض فرایند 

 مرحله 1 : ساختن یک وظیفه کاری (Work)  برای ارسال به صف وظایف (Task Queue)  که با تابع CreateThreadPool() میتوان  ایجاد کرد . ورودی های این تابع به ترتیب :

()CreateThreadpoolWork

1. نام تابع مورد نظر برای اجرا (در بالا نحوه ایجادش رو توضیح دادم)

2. ورودی تابع

 3.محیط اجرای این وظیفه کاری (جلوتردرباره این توضیح خواهم داد)

+ خروجی این تابع متغیری از نوع Work است که از طریق آن به Task مورد نظر دسترسی داریم .

نحوه استفاده :

()CreateThreadpoolWork

مرحله 2 : اضافه کردن این وظیفه کاری یا ارسال آن به استخرنخ برای اجرا. با تابع SubmitThreadPoolWork  :

Executing a work Thread with the SubmitThreadPoolWork function

فقط یک ورودی دارد : متغیری از نوع  Work  ، که در مرحله 1 ایجادش کردیم .+ حال آن تابع (my_func) رفت برای اجرا .

مرحله 3 : منتظر ماندن برای اتمام کار  که  2 روش وجود دارد :

1 . تابع Sleep()  

2 . تابع WaitForThreadpoolWorkCallbacks  :  2 آرگومان ورودی میگیرد .

آرگومان اول : منتظر اتمام کدام وظیفه کاری بمانم ؟ (منتظر پایان یافتن work  باش).

آرگومان دوم : فرض کنید چندین وظیفه (تابع) همزمان در حال اجراست . این آرگومان  اگر True  باشد ، به محض اینکه متغیر work  تمام شد ، بقیه وظایف درحال اجرا  را  هم کنسل میکند. اما اگر  False باشد ، منتظر پایان یافتن همه میماند .  

+ اگر این مرحله (3) وجود نداشته باشد ،  تابع  main نیز همروند یا موازی اجرا میشود . و اگر به پایان خود رسید ، همه نخ ها (حتی در حالت اجرا) از بین میروند .

مرحله 4 : آزاد کردن منابع در آخر برنامه با تابع  CloseThreadpoolWork

 فقط یک ورودی دارد : متغیر work

Freeing resources with the CloseThreadpoolWork() function

یک کد کامل با زبان  C  از این بخش :

Create ThreadPoolWork in C

مراحل ایجاد چندین وظیفه |  نخ کار (Work Thread) در استخرنخ پیشفرض

الگوریتم و نحوه کار مانند (ایجاد Work Thread) است . فقط بجای ایجاد یک work ، چندین work را ایجاد و به استخر نخ ارسال میکنیم. به برنامه زیر دقت کنید :

Creating multiple threads in the default thread pool

بجای استفاده تکی از متغیر work ، یک لیستی از work ها ایجاد کردیم و در حلقه تکرار وظایف را یکی یکی ایجاد و به استخرنخ ارسال کردیم. و در پایان ، داخل یک حلقه تکرار ،  منابع را آزاد کردیم .

+میتوانیم در یک فرایند چندین استخرنخ مستقل داشته باشیم . که هر کدام صف وظایف و نخ های خودشون رو داشته باشند ، و آنها را مدیریت کنند.

ایجاد استخرنخ (ThreadPool) مستقل در فرایند

مرحله 1 : ایجاد استخرنخ جدید با تابع CreateThreadpool  انجام میشود .

خروجی این تابع متغیری از نوع PTP_POOL است . که شناسه ای برای دسترسی به استخرنخ را برمیگرداند.

ورودی تابع، یک مقدار رزرو شده است ، و   ورودی ندارد .

Creating a new thread pool with the CreateThreadpool function

مرحله 2 : بعد از ساخت استخرنخ باید مشخص کنیم  Minimum & Maximum نخی که در این استخر قرار بگیرد چقدر است . که این کار با توابع :

SetThreadpoolThreadMinimum()  : دو آرگومان میگیرد . 1: شناسه استخرنخ (th_pol) 2 . کمترین نخ موجود در استخرنخ.

SetThreadpoolThreadMaximum()  : دو آرگومان میگیرد . 1: شناسه استخرنخ (th_pol) 2 . بیشترین نخ موجود در استخرنخ.

()SetThreadpoolThreadMinimum

اگر عملیات ، موفق آمیز بود . تابع مقدار  True برمیگرداند .

مرحله 3 : ایجاد محیط اجرای توابع بازگشتی ، در استخرنخ با متغیری از نوع TP_CALLBACK_ENVIRON

Defining a variable of type TP_CALLBACK_ENVIRON

مرحله 4 : بعد از اینکه محیط اجرا را تعریف کردید(مرحله 3) ، باید آن محیط اجرا را با  تابع (InitializeThreadpoolEnvironment) مقداردهی کنید. این کار به شما اجازه می‌دهد تنظیماتی مثل اولویت، یا Thread Pool خاص را اعمال کنید.

Initialization with InitializeThreadpoolEnvironment

این تابع آدرس متغیری از نوع TP_CALLBACK_ENVIRON به عنوان ورودی میگیرد . 

مرحله 5 : از جایی که فرایند یک استخرنخ پیشفرض دارد، و الان یک استخرنخ جدید ایجاد کردیم، باید آن را به سیستم معرفی کرده و به عنوان استخرنخ فرایند تنظیم کنیم با تابع ()SetThreadpoolCallbackPool میشود اینکار را انجام داد .

دو آرگومان میگیرد : 1 . محیط اجرای توابع 2. شناسه استخرنخ جدید

Setting the process thread pool with SetThreadpoolCallbackPool

مرحله 6 : آزاد کردن منابع در پایان برنامه

 1 . تابع  () DestroyThreadpoolEnvironment برای آزاد کردن منابع (محیط اجرا ) به کار میرود . این تابع یک ورودی دارد ، که آدرس متغیر(penc) محیط اجرا است.

DestroyThreadpoolEnvironment() function to release resources

 2 . تابع CloseThreadpool() نیز برای آزاد کردن منابع استخرنخ است . ووردی تابع شناسه استخرنخ است .

The CloseThreadpool() function is also used to free thread pool resources

یک کد کامل از ایجاد  استرنخ جدید و از بین بردن آن با یک وظیفه کاری :

Complete code of creating a new ThreadPool and destroying it with a Walker Thread

 تا الان وظایف (Tasks)  برنامه را ، نخ های کاری (Thread Worker) انجام میداند .

مثال : برنامه دانلود منیجر را در نظر بگیرید که درحال دانلود فایلی است . وسط های دانلود یهو ارتباط با سرور قطع میشه . دانلود منیجر به تعداد مشخصی ، مثلا 10 بار تلاش میکند تا دوباره با سرور ارتباط برقرار کند . برای پیاده سازی این سناریو ، مکانیزم های مختلفی وجود دارد که یکی از آنها  ThreadpoolTimer

است .

مراحل ایجاد Thread Timer  با Win32 API در زبان C & C++

نخ های نوع ThreadpoolTimer  ، وظیفه (Task) مورد نظر را بصورت دوره ای (مانند حلقه ها در برنامه نویسی (for)) ، یا یکبار انجام میدهند .  طریقه استفاده به این شکل است :

مرحله اول : یک محیط اجرا برای توابع برگشتی تعریف میکنیم . (مانند مرحله 3 و 4 بخش : تعریف استخرنخ جدید | مستقل)

Initialization with InitializeThreadpoolEnvironment

مرحله 2 : ایجاد آبجکتThread Timer  با تابع ()CreateThreadpoolTimer :

ورودی ها :

 1. تابع مورد نظر برای اجرا

 2. ورودی های تابع

3. آدرس متغیر محیط اجرا (tce)

 خروجی : شناسه ای برای دسترسی به شی

Create a Thread Timer object with the CreateThreadpoolTimer() function

مرحله 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   برای پیکربندی زمان اجرا .

Defining a variable of type FILETIME

نحوه تعریف کردن زمان مطلق برای FILETIME

1 . با استفاده از تعریف متغیری از نوع SYSTEMTIME زمان میلادی به زبان انسان را دریافت میکنیم و بعد میتوانیم آن متغیر را با زمان دلخواهمون مقداردهی کنیم . اگر قصد مقداردهی یکی از پارامتر هارا نداشتید . میتوانید صفر قرار بدهید . 

Defining a variable of type SYSTEMTIME

نحوه مقداردهی بقیه پارامتر ها :

Initializing a variable of type SYSTEMTIME

نکته : اگر قصد دارید تایمری را برای ماه آینده ، فردا یا چند ساعت آینده ، ست کنید ، نیازی نیست همه پارامتر ها را بصورت دستی مقداردهی کنید ، با تابع GetSystemTime میتوانید زمان حال را دریافت کرده و در متغیری که بالا تعریف کردیم ذخیره کنید ، بعد پارامتر زمانی را که نیاز دارید را تغییر بدهید .

ورودی تابع GetSystemTime : آدرس متغیری از نوع SYSTEMTIME

مثال : قصد داریم تایمر را برای امروز ، همین ساعت ، اما دقیقه متفاوت ست کنیم . تاریخ امروز (2025/8/10 11:10:32) ،  تاریخی که میخایم ست کنیم : (2025/8/10 11:53:00)

 این کار را انجام میدهیم :

Get system time with the GetSystemTime function

چون فقط دقیقه و ثانیه قرار بود عوض بشوند ، این دو پارامتر را بعد از گرفتن زمان سیستم و قرار دادن آن در متغیر st ، ثانیه و دقیقه را در آن متغیر ، تغییر دادیم . (2025/8/10 11:53:00)

2. تبدیل تاریخ انسانی (2025/8/10 11:53:00) به تاریخ کامپیوتری با تابع SystemTimeToFileTime

ورودی ها : 1. آدرس  SYSTEMTIME 2. آدرس FILETIME

Converting a calendar date to system time with the SystemTimeToFileTime function

خلاصه زمان مطلق : یک متغیر از نوع SYSTEMTIME برای دریافت زمان انسانی تعریف کردیم ، با تابع SystemTimeToFileTime آن را به زمان سیستم تبدیل کردیم ، و در متغیری از نوع FILETIME ذخیره کردیم .

مرحله 5 : اجرایی کردن مرحله 3 بعد از تکمیل پارامتر ها .

Excuted a timer object with the SetThreadpoolTimer function

ورودی ها به ترتیب :

1. شی تایمری که ایجاد کردیم (مرحله 2)

2. زمان شروع تایمر که از نوع FILETIME است ، (مرحله 4)

3 . چون مقدارش 0 است ، یعنی تابع فقط یکبار اجرا میشود . اگر 2000 بود ، هر 2 ثانیه یکبار بصورت دوره ای اجرا میشد .

4 . حداکثر زمان تاخیر برای اجرای تابع

نمونه کامل از این کد :

Complete Thread Timer code in C language

تا الان ما ، زمان مطلق را یاد گرفتیم  ، بهتره نگاهی به زمان نسبی هم بیاندازیم . 

نحوه تعریف کردن زمان نسبی برای FILETIME

1. تعریف متغیری از نوع FILETIME   برای پیکربندی زمان اجرا .

Defining a variable of type FILETIME

2 . تعریف  متغیری از نوع   ULARGE_INTEGER برای ذخیره زمان حال (زمان انسانی)

Defining a variable of type ULARGE_INTEGER

3 . ذخیره کردن زمان اجرا در پارامتر  ul.QuadPart ، که باید TypeCast شود به (ULONGLONG) ، و منفی باشد (بنا به دلایلی که در بالا گفتیم).

توجه : زمان بر حسب نانو ثانیه است .

Save runtime in QuadPart parameter

4 . تبدیل این زمان انسانی به زمان سیستم :

Converting this human time into system time

5 . اجرا کردن تایمر با تابع SetThreadpoolTimer بعد از تکمیل پارامتر هاش :

Running a timer with the SetThreadpoolTimer function

ورودی ها به ترتیب :

1. شی تایمری که ایجاد کردیم (مرحله 2)

2. زمان شروع تایمر که از نوع FILETIME است ، و بصورت نسبی مقدار دهی کردیم .

3 . چون مقدارش  2000 است ، هر 2 ثانیه یکبار بصورت دوره ای اجرا میشود .

4 . حداکثر زمان تاخیر برای اجرای تابع ، که 0 هست . و به معنی بی درنگ اجرا شدن است .

 نمونه ای از کد کامل :

Complete ThreadpoolTimer code in C language

مفهوم تابع WaitForThreadpoolTimerCallbacks ، منتظر ماندن برای تمام شدن تابع درحال اجرا .

آرگومان اول : منتظر اتمام کدام تایمر بمانم ؟ (منتظر پایان یافتن timer  باش).

آرگومان دوم : فرض کنید چندین تایمر (تابع) همزمان در حال اجراست . این آرگومان  اگر True  باشد ، به محض اینکه متغیر timer ما اجراش تمام شد ، بقیه  تابع های  درحال اجرا را هم کنسل میکند. اما اگر  False باشد ، منتظر پایان یافتن همه میماند ، حتی اگر تابع ما اجراش تمام شده باشد .  

 مفهوم تابع CloseThreadpoolTimer ، آزاد کردن منابع سیستم است . 

نکته :  معمولا توابعی از نوعWaitForThreadpool….. (timer or work or wait) واقعا منتظر پایان اجرای تابع نمی مانند (یعنی تابع  main بلاک نمیشود و به اجراش ادامه میدهد ) . و معمولا از این توابع ، هنگام آزاد کردن منابع استفاده میکنند ، تا مطمن باشند توابع (نخ) در حال اجرا ، کارشون به اتمام رسیده . اما اگر تابع  main  به اجراش ادامه بده ، امکان دارد برنامه به آخر برسد و  سیستم کال  exit  را فراخوانی کند ، که در این حالت همه منابع از فرایند گرفته میشود و تمام نخ های در حال اجرا kill میشوند . پس اگر نیاز داشتیم ، تا تمام نشدن تابع مورد نظر برنامه بلاک شود و یا منتظر بماند تا آن تابع کارش را انجام دهد ،  از  Event  استفاده میکنیم .

نحوه تعریف کردن Event

به معنی رویداد است . بیشتر برای همگام سازی بین نخ ها استفاده میشود. مثال :فرض کنید یک نخی به دیتای یک نخ دیگر نیاز دارد و منتظر پایان یافتن آن است ، هروقت کار اون نخ تمام شد ، میاد و از event  برای مطلع کردن نخ در حال انتظار استفاده میکند .

مرحله 1 : اول Threadpool work|timer|wait  را ایجاد و ارسال میکنیم به استخرنخ برای اجرا .

Create Threadpool work|timer|wait

مرحله 2 : با تابع CreateEventW یک event  ایجاد میکنیم .

ورودی ها :

 1. تنظیمات امنیتی ، که معمولا مقدار NULL به معنی استفاده تنظیمات امنیتی پیشفرض است .

2.  اگر True  باشد ، به معنی ریست دستی است . اگر  false باشد ، بعد از آزاد شدن نخ بصورت خودکار ریست میشود .

3 .  وضعیت اولیه رویداد . موقع ساخت فعال باشد (True)،  یا غیرفعال باشد (False).

4 . اسم دلخواه برای event ،که  اگر NULL باشد ، بدون نام خواهد بود .

خروجی : شناسه ای از نوع Handle برمیگداند . برای دسترسی به وضعیت event

We create an event with the CreateEventW function

توجه : متغیر hEvent که از نوع HANDLE است ، باید بصورت Global تعریف شود . تا در همه توابع بهش دسترسی داشته باشیم . مانند کد زیر :

The hEvent variable should be defined globally

مرحله 3 : سپس با تابع WaitForSingleObject منتظر فعال شدن آن hEvent می مانیم .

ورودی ها : 1. handle  

 2 . مدت زمان منتظر ماندن (به میلی ثانیه )، (INFINITE به معنای تا بینهایت منتظر ماندن است) . اگر شما 10000 وارد کردید ، به معنی آن است که 10 ثانیه منتظر بمون .

Wait for that event whit WaitForSingleObject function.

+از این تابع برای متتظر ماندن پایان فرایند فرزند نیز استفاده میشود .

مرحله 4 : فعال سازی event با تابع SetEvent  انجام میشود . مثال : فرض کنید تابع (نخ) ، کارش را انجام داده و به پایان رسیده ، و نخ دیگری منتظر این تابع مانده است ، با استفاده از SetEvent  به تابع در حال انتظار، اطلاع میدهد که کارش را به اتمام رسونده .

ورودی : یک  Handle  میگیرد .

Activation event with the SetEvent function

به محض اینکه این تابع (setEvent)فراخوانی شد ، تابع در حال انتظار (main) از بلاک خارج میشود . (از حالت انتظار خارج میشود)

در نهایت با تابع ResetEvent    ، Handle (hEvent)  غیرفعال میشود ، برای استفاده دوباره .

+تابع WaitForSingleObject تا زمانی که hEvent غیرفعال است ، منتظر میماند . وقتی که فعال شد (با تابع SetEvent  ) ، انگار جوابش را گرفته و دیگر منتظر نمی ماند .

+یک تابع دیگر برای منتظر ماندن برای تعدادی handle ها وجود دارد، به اسم WaitForMultipleObject که به این شکل عمل میکند :

ابتدا تعداد Handle هارا میگیرد ، بعد لیست Hnadle هارا میگیرد ، پارامتر سوم اگر True باشد ، برای همه منتظر میماند ، درغیراینصورت منتظر اولین Handle که تمام شود ، میماند . پارامتر آخر نیز مدت زمان انتظار است. 

کد کامل Event:

Complete event code in C language

فرض کنید ، برنامه دانلود منیجر در حال دانلود فایلی است که یهو ارتباط قطع میشود ، دانلود منیجر به تعداد مشخصی(مثلا 10 بار)،سعی میکند با سرور ارتباط بگیرد،که میتوانیم این نخ را از نوع ThreadPoolTimer

تعریف کنیم تا کد مشخصی را بصورت دوره ای اجرا کند .  حال دانلود منیجر میخواهد ، وقتی به سرور متصل شد ، یک تابع مشخصی را اجرا کند .(کار این تابع ، دریافت بقیه فایل مورد نظر است) پس ما نیاز به نوع نخی داریم که ، منتظر یک رخدادی بماند (ارتباط دانلود منیجر با سرور ) ، و هنگامی که این اتفاق افتاد ،بیاد و تابع مذکور را اجرا کند . 

نحوه تعریف کردن Thread Wait

تا زمانی که Event رخ دهد یا Handle فعال شود ، این تابع منتظر میماند ، و هروقت رخ داد یک تابع مشخصی را اجرا میکند .

مرحله (1). نحوه تعریف تابع برای اجرا توسط Thread Wait :

Define a function to be executed by Thread Wait

آرگومان (wrsult) TP_WAIT_RESULT نوع داده ای است که مشخص میکند ، آیا اجرای تابع توسط Thread Wait در زمان مقرر شده انجام یافته یا timeout رخ داده است .

مرحله (2). تعریف محیط اجرا برای تابع های  برگشتی

Initialization with InitializeThreadpoolEnvironment

مرحله (3). ایجاد نخ  انتظار با تابع CreateThreadpoolWait :

ورودی ها :

1. تابع ای که بعد از رخداد اجرا میشود.

2. ورودی آن تابع

 3. محیط اجرا

خروجی : شناسه ای برای دسترسی به (thread wait)

Create a waiting thread with the CreateThreadpoolWait function

 مرحله (4). ایجاد Event  ، برای شبیه سازی رخداد

()Create Event

 مرحله (5). ارسال نخ انتظار (thread wait) به استخرنخ ، جهت اجرا با تابع SetThreadpoolWait 

Excuted a ThreadWait object with the SetThreadpoolWait function

ورودی ها :

1 . شناسه thread wait (pwit)

2 . منتظر فعال شدن کدام handle باشم؟ (hevent که در بالا ایجاد کردیم)

3 . چقدر منتظر رویداد بمانم ؟:

این ورودی یک زمان از نوع  FILETIME (در بالا توضیح دادیم) میگیرد . (مثلا 10  ثانیه) ، اگر در این 10 ثانیه Handle فعال شد، wrsult با WAIT_OBJECT_O مقداردهی میشود ، در غیر اینصورت Timeout رخ میدهد و wrsult با WAIT_TIMEOUT مقداردهی میشود .

Checking TP_WAUT_RESULT values

+نحوه ایجاد FILETIME ، که 2 ثانیه منتظر رویداد میماند ، در غیر اینصورت TimeOut برمیگرداند.

Create a FILETIME variable

مرحله (6). فعال سازی Handle  ، به منظور شبیه سازی رویداد با تابع SetEvent :

Activation with the SetEvent function

+قبل از این تابع ، میتوانیم با تابع  Sleep اجرای برنامه را شبیه سازی کنیم .

Sleep() functions

+این تابع ، نخ اصلی برنامه را (Primary Thread) ، 40 ثانیه بلاک میکند . واحدش به میلی ثانیه است .

مرحله (7) . آزادسازی منابع :

Release of resources

کد کامل :

Complete ThreadPoolWait code in C language

مرسی که تا اینجای مقاله با من بودید ، امیدوارم مطالب به دردتون خورده باشند ، پیشنهاد میکنم تمام مطالب را با مثال های واقعی (پیاده سازی صفحه چت با کلاینت های بیشتر یا شبیه سازی دریافت آبجکت های یک صفحه ی html بصورت موازی ) تمرین کنید .