رفتن به محتوای اصلی
تا پایان: استفاده از تخفیف
عمومی

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

چرا Async

آکادمی بدیعی

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

مقدمه

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

و برای درک اینکه چرا بوجود آمد، اول باید درک کنیم کدهای یک برنامه چگونه اجرا می شوند و برای این منظور هم باید اول با چند مفهوم مثل IO-Bound ، CPU-Bound و Thread در برنامه نویسی آشنا شویم.

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

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

Blocking چیه و دقیقاً چه آسیبی می‌زند؟

IO Completion Port چه نقشی دارد؟

Thread و ThreadPool چی هستن و چه ارتباطی با Async دارن؟

Async چیه و دقیقاً چه چیزی را بهینه می‌کند؟

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

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

I/O-Bound و CPU-Bound

I/O-Bound

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

Database دیتابیس

File system فایلها

HTTP ارتباط با وب

Network ارتباط با شبکه

External API فراخوانی 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();

در این حالت :

درخواست به دیتابیس ارسال می‌شود

Thread تا زمان پاسخ صبر می‌کند

هیچ کار مفیدی انجام نمی‌دهد

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)

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

شما عملیات I/O را شروع می‌کنید (مثلاً خواندن از پورت یا دیتابیس)

OS یا سیستم عامل، کار را به سخت‌افزار واگذار می‌کند

Thread آزاد می‌شود

وقتی عملیات تمام شد، OS به ThreadPool اطلاع می‌دهد

یک Thread آزاد ادامه کار را اجرا می‌کند

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

این یعنی :

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

IO Completion Ports

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

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

در async :

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

درخواست HTTP وارد می‌شود

عملیات I/O شروع می‌شود

Thread آزاد می‌شود و به ThreadPool برمیگردد

و تردها می‌توانند درخواست‌های بیشتری سرویس دهند

و وقتی پاسخ 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 و بعد به کدنویسی آن می پردازیم.

نویسنده: آکادمی برنامه‌نویسی بدیعی

این مقاله را هم‌رسانی کنید: