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();
در این حالت:
- درخواست به دیتابیس ارسال میشود
- 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 و بعد به کدنویسی آن می پردازیم.
دانلود فایل پی دی اف







