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