برنامه نویسی Async ناهمگام

Async چیه و چه کاربردی داره

چرا Async

Async چیه و چرا متولد شد؟

مقدمه

بهتره اول با معنی و مفهوم Async کاری نداشته باشیم و ببینیم که چرا بوجود آمد و چه مشکلی را حل کرد.

async یک قابلیت تزئینی نیست بلکه یک راه‌حل معماری برای حل بحران مقیاس‌پذیری است.

در پایان این مقاله، بخوبی میتوانی اینها را توضیح بدهی:

  • مشکل مدل قدیمی سرورها چه بود که Async  پیدا شد؟
  •  Blocking چیه و  دقیقاً چه آسیبی می‌زند؟
  • IO Completion Port  چه نقشی دارد؟
  • Thread  و ThreadPool چی هستن و چه ارتباطی با Async دارن؟
  • Async چیه و دقیقاً چه چیزی را بهینه می‌کند؟

بدون فهم این‌ها، async فقط یک کلمه کلیدی است.
با فهم این‌ها، async تبدیل می‌شود به ابزار مهندسی نرم افزار.


 

 

  • I/O-Bound vs CPU-Bound

مهم‌ترین تفکیک برای فهم async

I/O-Bound

کارهایی که منتظر منبع خارجی هستند مثل:

  • Database
  • HTTP
  • File system
  • Network
  • External API

در این عملیات ها، ۹۰٪ زمان صرف انتظار برای اون منبع خارجی می شود.


CPU-Bound

کارهایی که CPU را درگیر می‌کنند:

  • محاسبات ریاضی
  • رمزنگاری
  • پردازش تصویر
  • فشرده‌سازی

در اینجا انتظار وجود ندارد ، و CPU واقعاً در حال کار است.

 
  • تفاوت Concurrency  و  Parallelism

دو مفهومی که همیشه اشتباه گرفته می‌شوند

Parallelism — یعنی اجرای همزمان واقعی

هر هسته یا Core  در CPU در هرلحظه امکان اجرای یک عملیات را دارد و Parallelism  یعنی چند کار واقعاً همزمان روی چند هسته CPU اجرا شوند بشرطی که CPU  چند هسته ای باشد.

مثال:

  • سرور ۸ هسته‌ای
  • ۸ عملیات پردازش تصویر سنگین
  • هر عملیات روی یک Core

واقعاً محاسبات همزمان انجام می‌شوند یا Parallelism و هدف افزایش سرعت محاسبات  CPU-bound


Concurrency —  مدیریت همزمان چند کار(همزمانی منطقی)

درConcurrency  چند کار در یک بازه زمانی مدیریت می شوند، اما لزوماً همزمان اجرا نمی شوند.

مثال:

  • یک درخواست HTTP
  • یک ارتباط به دیتابیس
  • یک ارتباط به API خارجی

در اینجا سیستم باید چند کار با هم مدیریت کند، تا معطلی و انتظار در برخی عملیاتها مثل ارتباط با دیتابیس و یا Api ، باعث تاخیر روی بقیه عملیات ها و بقیه درخواست ها نشوند.

این حالت یعنی Concurrency و استفاده بهینه از منابع در کارهای I/O-bound


تفاوت ها

Concurrency

Parallelism

ویژگی

نه

بله

نیاز به چند CPU

I/O

CPU

مناسب برای

افزایش throughputیا توان عملیاتی

افزایش سرعت پردازش

هدف

async  مربوط به Concurrency  است، نه Parallelism


 

 

  • Thread  چیست؟

Thread کوچک‌ترین واحد اجرایی در یک برنامه است که سیستم‌عامل زمان‌بندی می‌کند یا به عبارتی دیگر، یک مسیر مستقل برای اجرای دستورات است.

به زبان ساده تر، در یک برنامه ی درحال اجرا (Process) ، حداقل یک Thread وجود دارد و Thread ها مثل کارگران درون یک کارخانه، مسئول اجرای دستورات درون آن برنامه هستند. در واقعا دستورات و کدهایی که اجرا می‌شوند، روی یک Thread قرار میگیرند و این ترد توسط یک Core از CPU اجرا می شود و اگر در لحظه تعداد تِرِدها بیشتر از تعداد Core های CPU باشد عمل Context Switch یعنی جابجایی تردها، اتفاق میفتد.


هر Thread:

  • Stack اختصاصی دارد
  • Context اختصاصی دارد
  • توسط OS مدیریت می‌شود
  • مصرف حافظه دارد
  • هزینه Context Switch که بین تردها اتفاق میفته

 

ThreadPool — چیه و ارتباطش با Thread

یک واقعیت اینه که ساخت Thread

  • زمان‌بر است
  • حافظه مصرف می‌کند
  • مدیریت OS را درگیر می‌کند
  • Context switching ایجاد می‌کند

برای همین .NET یک استخر Thread دارد بنام  ThreadPoolیعنی محل تردهای آماده

 

ویژگی‌های  ThreadPool

  • مجموعه‌ای از Threadهای آماده
  • تردهای Reusable یعنی قابل استفاده مجدد
  • مدیریت شده توسط Runtime
  • بهینه برای عملیات کوتاه‌مدت

در سرورهای وب، اکثر درخواست‌ها توسط ThreadPool اجرا می‌شوند.


 

 

  • مدل قدیمی: Thread per Request

در معماری قدیمی وب سرورها:

در هر درخواستِ HTTP(Request) ، یک Thread کامل تا پایان آن Request اشغال می‌ماند و به تنهایی مسئول اجرای همه عملیات بود.

فرض کنیم:

  • ۱۰۰۰ کاربر همزمان
  • هر درخواست ۲۰۰ میلی‌ثانیه منتظر پاسخ دیتابیس

نتیجه:

  • ۱۰۰۰ Thread لازم
  • ۱۰۰۰ Thread منتظر دیتابیس
  • CPU تقریباً بیکار، چون تردها اشغال شده اند ولی بیکار و منتظر هستن تا عملیات IO انجام شود
  • ThreadPool پر
  • Memory مصرف بالا
  • Context switching شدید

سیستم منابع را برای «صبر کردن» مصرف می‌کند که این فاجعه مقیاس‌پذیری است.


 

 

  • . Blocking دقیقاً چیست؟

Blocking  یعنیThread شروع به انجام کاری می‌کند و تا پایان آن کار، نمی‌تواند هیچ کار دیگری انجام دهد، یعنی مثلا بخاطر گرفتن یک دیتا از دیتابیس یا ذخیره دیتا در دیتابیس یا حتی فراخوانی و گرفتن پاسخ از یک api  ، مدت زمانی که آن منبع خارجی معطلی دارد موجب معطل شدن Thread جاری می شود و در این مدت ترد بیکار و منتظر بدون هیچ اقدامی سبب Block شدن برنامه می شود.

مثال:

var users = _dbContext.Users.ToList();

در این حالت:

  1. درخواست به دیتابیس ارسال می‌شود
  2. Thread تا زمان پاسخ صبر می‌کند
  3. هیچ کار مفیدی انجام نمی‌دهد

Thread اشغال است ولی عملاً بیکار و این یعنی Blocking

در سیستم‌های I/O-heavy، این اتلاف منابع زیاد است.


مشکل واقعی سرورها چه بود؟

فرض کنید:

  • ۵۰۰۰ درخواست همزمان
  • هر درخواست:
    • ۱۵۰ms  معطل دیتابیس
    • ۱۰۰ms  معطل API خارجی
    • ۲۰ms  معطل پردازش داخلی

کل زمان ۲۷۰ms
اما زمان CPU واقعی یعنی پردازش داخلی اصلی فقط ۲۰ms است و بقیه زمان منتظر پاسخ از منابع خارجی ست یعنی همون IO-Bound ها.

مدل Blocking:

  • ۵۰۰۰ Thread لازم
  • ۵۰۰۰ Thread در این مدت زمان منتظر هستند
  • ThreadPool اشباع می‌شود چون همه تردها مصرف میشن برای اجرای هرعملیات و منتظر هستن.
  • درخواست‌های جدید رد می‌شوند
  • latency افزایش می‌یابد
  • سیستم ناپایدار می‌شود

مشکل این نبود که CPU ضعیف است بلکه مشکل این بود که Threadها بیهوده نگه داشته می‌شدند.


 

 

 

  • راه‌حل : IO Completion Ports

در ویندوز مکانیزمی وجود دارد به نام IO Completion Ports (IOCP)

ایده آن بسیار هوشمندانه است:

  1. شما عملیات I/O را شروع می‌کنید (مثلاً خواندن از پورت یا دیتابیس)
  2. OSیا سیستم عامل، کار را به سخت‌افزار واگذار می‌کند
  3. Thread آزاد می‌شود
  4. وقتی عملیات تمام شد، OS به ThreadPool اطلاع می‌دهد
  5. یک Thread آزاد ادامه کار را اجرا می‌کند

و این یعنی، هیچ Threadی بیکار و منتظر I/O نمی‌ماند.

این یعنی:

* Thread فقط وقتی استفاده می‌شود که واقعاً کاری برای انجام دادن وجود دارد و ASP.NET Core  و .NET Runtime  دقیقاً روی همین مدل ساخته شده‌اند.


IO Completion Ports

یک معماری سطح پایین برای مدیریت I/O به‌صورت  asynchronousاست.

در ASP.NET Core روی ویندوز، Kestrel(وب سرور) از IOCP استفاده می‌کند.

در  async:

اینجا مفهوم  مقیاس پذیری یا Scalability  متولد می‌شود.

  1. درخواست HTTP وارد می‌شود
  2. عملیات I/O شروع می‌شود
  3. Thread آزاد می‌شود و به ThreadPool برمیگردد
  4. و تردها می‌توانند درخواست‌های بیشتری سرویس دهند
  5. و وقتی پاسخ IO رسید:
    • یک Thread از ThreadPool ادامه کار را اجرا می‌کند

Thread در طول انتظار، اشغال و بیکار نیست.

این یعنی:

  • تعداد Thread کمتر
  • مصرف حافظه کمتر
  • throughput  یا توان عملیاتی بیشتر
  • latency پایدارتر
  • مقیاس‌پذیری واقعی(توانایی سیستم برای مدیریت بار بیشتر بدون افت شدید کارایی)

نتیجه مهم:

Async یا مدیریت ناهمگام ، برای عملیات های I/O-Bound ساخته شده، تا در اون زمان هایی که انتظار و معطلی وجود داره، Thread  بیکار نماند و به کارهای دیگه برسد پس Async مناسبه برای عملیات های       IO-Bound ،  (نه برای  CPU-Bound )

 
  • چرا این برای ASP.NET Core حیاتی است؟

در ASP.NET Core:

  • هزاران request همزمان طبیعی است
  • اکثر عملیات I/O هستند
  • Thread منبع محدود است
  • مقیاس‌پذیری حیاتی است

بدون async، سیستم خیلی سریع به ThreadPool starvation یعنی کم آمدن تردها، می‌رسد.

با async:

  • با ۵۰۱۰۰ Thread
  • می‌توان ده‌ها هزار connection را مدیریت کرد

به همین دلیل است که در معماری مدرن .NET، Async انتخاب نیست بلکه استاندارد است.

تصویر ذهنی نهایی

بدون async:

Request → Thread → Wait → Waste

با async:

Request → Start IO → Free Thread → Resume Later

اگر async نبود چه می‌شد؟

  • سرورهای وب به هزاران Thread نیاز داشتند
  • مصرف RAM انفجاری می‌شد
  • context switching CPU را نابود می‌کرد
  • مقیاس‌پذیری واقعی تقریباً غیرممکن بود

 

  • Async  چیه؟

اول بگیم Async یعنی چی نیست

Async یعنی موازی اجرا شدن نیست
Async یعنی چند Thread داشتن نیست
Async یعنی سریع‌تر اجرا شدن نیست
Async یعنی همزمان اجرا شدن نیست

این‌ها برداشت‌های اشتباه رایجن.


معنی دقیق Async در برنامه‌نویسی

از کلمه Asynchronous یعنی:

اجرای کاری بدون اینکه جریان فعلی مجبور باشد تا پایان آن صبر کند.

پس بجای "ناهمزمان"، Async  یعنی:

اجرای غیرمسدودکننده (Non-blocking execution).

یعنی من این کار را شروع می‌کنم، ولی تا وقتی که کامل نشده، Thread را معطل و بیکار نگه نمی‌دارم.

مدلی از اجرا که در آن، هنگام انتظار برای یک عملیات طولانی، Thread آزاد می‌شود و ادامه اجرای کد پس از اتمام عملیات انجام می‌شود.


 

 

  • جمع‌بندی نهایی

در این مقاله فهمیدیم:

  • Concurrency  با Parallelism فرق دارد
  • Thread  و ThreadPool چیه
  • Thread per Request مقیاس‌پذیر نیست
  • Blocking باعث هدررفت Thread می‌شود
  • بیشتر عملیات وب I/O-Bound هستند
  • IO Completion Port  معطلی و انتظار را از Thread جدا می‌کند
  •  async برای آزادسازی Thread در زمان انتظار ساخته شده

async برای سریع‌تر کردن CPU نیست.
برای آزاد نگه داشتن Thread در زمان انتظار است.

تا اینجا یاد گرفتیم که Async چیه و چرا بوجود آمد و در مقالات بعدی به نحوه کارکرد و پشت صحنه Async و ارتباطش باTask  و await  و بعد به کدنویسی آن می پردازیم.


دانلود فایل پی دی اف
اشتراک