آموزش برنامه نویسی سوئیفت (Swift): بستار و Grand Central Dispatch – بخش یازدهم
در بخش قبلی این سری مطالب آموزش سوئیفت مجله فرادرس با ساختار کد، خوانایی و موارد دیگری آشنا شدیم. گرچه این مفاهیم چندان فنی محسوب نمیشوند؛ اما اگر به این مهارتها مجهز باشید، پروژههایتان مقیاسپذیر میشوند و میتوانید در مورد روشهای سازماندهی کدها راحتتر فکر کنید. ساختار کد بین پروژههای مختلف متفاوت است، زیرا توسعهدهندگان به ندرت در مورد روش طرحبندی کدهای خودشان صحبت میکنند مگر این که شما عملاً وارد نخستین شغل برنامهنویسی خود شده باشید. حتی در این صورت نیز در اغلب موارد توسعهدهندگان توضیح زیادی نمیدهند و انتظار دارند که شما این مفاهیم را خودتان دریابید. در مواردی که بخواهید اپلیکیشنهای خودتان را شخصاً بنویسید نیز آموزشهای چندانی در این زمینه نخواهید داشت. در این نوشته قصد داریم به توضیح بستار و Grand Central Dispatch بپردازیم.
در مطلب قبلی که در مورد خوانایی کد صحبت میکردیم، فرصت کافی برای پرداختن به همه موارد وجود نداشت؛ اما مبانی قضیه ارائه شد و از این رو زمانی که به کدهایی که قبلاً نوشتهاید رجوع کنید، احتمالاً به سرعت میتوانید دریابید که چه چیزی میخواستهاید بنویسید. این مبحث بزرگی است و شاید یک نوشته کامل را بتوان به بحث ساختار کد و خوانایی آن اختصاص داد. اما برای این کار فرصت کافی نداریم و صرفاً با ارائه سرفصلها از آنها عبور میکنیم. در این نوشته به بررسی چند مفهوم دیگر سوئیفت پرداختهایم که شما را اندکی بیشتر با چارچوب کدنویسی در این زبان برنامهنویسی آشنا میکند.
Grand Central Dispatch
Grand Central Dispatch روشی است که اپل برای مدیریت صفهای dispatch مورد استفاده قرار میدهد. سه نوع صف وجود دارند:
صف سریال
در این روش کارهایی که به صف ارسال میشوند به همان ترتیبی که دریافت شدهاند اجرا میشوند و آن را میتوان نوعی رویه «ورودی اول، خروجی اول» (FIFO) انگاشت. این نوع صف به نام صف dispatch خصوصی نیز شناخته میشود.
صف همزمان
در این روش کارهایی که به صف ارسال میشوند، به صوت همزمان و موازی اجرا میشوند. ترتیب آغاز شدن هر کار، همان ترتیب اضافه شدن آن به صف است. تفاوت اصلی بین این نوع و نوع سریال در این است که در صفهای سریال، وظیفه بعدی تا زمانی که وظیفه قبلی پایان نیافته باشد، آغاز نمیشود. در صفهای همزمان، وظیفه بعدی ممکن است به همان میزانی که وظیفه قبلی زمان برده است به زمان برای کامل شدن نیاز نداشته باشد و این احتمال وجود دارد که وظیفه بعدی از وظیفه قبلی زودتر پایان گیرد. این نکته را زمانی که میخواهید در مورد نوع صف مورد استفاده تصمیمگیری کنید، به خاطر داشته باشید. این نوع صف به نام صف dispatch سراسری نیز شناخته میشوند.
صف dispatch اصلی
این همان نخ اصلی اپلیکیشن یا همان جایی است که اپلیکیشن قرار دارد. زمانی که کد خود را درون ()viewDidLoad در یک کنترلر ویو قرار میدهید، این صف همه کارها را انجام میدهد.
اگر همه این مواردی که مطرح شد را کنار بگذاریم و بخواهیم توضیح دهیم که همه این صفها در رابطه با سختافزار چگونه عمل میکنند، میتوانیم به تصویر زیر رجوع کنیم:
تعاریف مرتبط
در ادامه توضیحی در مورد شیوه چیدمان وظایف در زمان اضافه شدن به صف ارائه میکنیم. پیش از ادامه چند تعریف را ملاحظه کنید:
حلقه اجرا
برنامه شما تا زمانی که از آن نخواهید، هرگز متوقف نخواهد شد و زمانی که در حال اجرا است در یک حلقه while قرار دارد تا زمانی که به انتها برسد. در برخی موارد لازم است که چیزهایی را پردازش کنیم. در برخی موارد دیگر صرفاً یک گذر ساده از حلقه اتفاق میافتد و هیچ کاری اجرا نمیشود.
نخ
به بیان ساده نخ به وظایف منفرد گفته میشود. اگر این وظیفه نیازمند روال خاص خود باشد، این وظیفه در نخ دیگری مورد پردازش قرار میگیرد (این مفهوم نباید با هسته CPU اشتباه گرفته شود).
CPU
مغز رایانه است و آن قطعهای است که محاسبات را با یک یا چند هسته اجرا میکند.
هسته
منظور از هسته یک بخش فیزیکی یا منطقی از پردازنده است که کد شما را خوانده و نتایج را بازگشت میدهد. هر هسته چندین نخ سختافزاری دارد که میتواند برای وظیفهای که به آن ارسال شده مورد استفاده قرار گیرد.
CPU Clock Speed
سرعتی است که پردازنده اقدام به خواندن مقادیر 0 و 1 یا باینری میکند. اگر تاکنون کنجکاو بودهاید که منظور از 1 گیگاهرتز چیست، باید بگوییم که گیگا پیشوندی به معنی میلیارد است و از این رو 1 گیگاهرتز برای هر هسته به معنی این است که هر هسته CPU میتواند در ثانیه 1 میلیارد صفر و یک را بخواند. یک رایانه معمولی امروزه سرعت پردازنده 2.8 گیگاهرتز و عموماً 8 هسته دارد. این بدان معنی است که این رایانه میتواند 22.4 = 8 * 2.8 میلیارد یک و صفر را در هر ثانیه در سرعت ساعت پایه خود بخواند. اگر کنجکاو هستید که سرعت ساعت CPU چه ارتباطی با حلقه اجرا دارد، پاسخ این است که ساعت برخی تیکها را اجرا میکند که هر تیک یک مقدار 1 یا صفر را میخواند.
چرخه دستورالعمل
در برخی موارد از اصطلاحی به نام چرخه استفاده میشود. منظور از چرخههای CPU مجموعه دستورالعملهایی هستند که CPU آنها را میخواند تا وظایفی را از طریق یک سری از تیکها اجرا کند.
بر اساس مثال فوق، هسته 0 مسئول اپلیکیشن در حال اجرای شما است. نخ اصلی، اپلیکیشن شما است. همان طور که میبینید ما حلقه اجرا را اضافه کردهایم تا نشان دهیم که هرگز تا زمانی که با خروج از اپلیکیشن آن را متوقف نکنید از چرخش بازنمیایستد. زمانی که اپلیکیشن شما اجرا میشود، وظایف زیادی ایجاد میکند که باید به صورت ناهمگام و یا در صورتی که اپلیکیشن بخواهد کار دیگری انجام دهد، به صورت همزمان، اجرا شوند. به این منظور باید یک نخ جدید در هسته دیگری ایجاد کنیم.
اگر آن را به یک صف سراسری (همزمان) ارسال کنیم، روی هسته موجود بعدی قرار میگیرد و به محض دریافت شدن مورد پردازش قرار میگیرد. در صورتیکه به یک صف خصوصی (سریال) ارسال کنیم، روی هسته بعدی موجود قرار میگیرد و به محض این که هسته بتواند همه تمرکز خود را روی این وظیفه قرار دهد، مورد پردازش قرار میگیرد. این شرایط معمولاً در طی چندین میلیثانیه ایجاد میشود.
روی سیستمهای قدیمی، زمانی که تنها یک هسته پردازنده وجود دارد، این وظایف میتوانند به صورت همزمان روی یک هسته اجرا شوند، مشکل اینجا است که در این حالت، هر وظیفه میتواند از سوی سیستم متوقف شود تا سیستم بتواند بین آنها کارهای خود را نیز انجام دهد.
در سیستمهای جدیدتر، این مشکل همچنان وجود دارد؛ اما با وجود 8 هسته که میتوانند به اجرای وظایف بپردازند، مشکل چندان هویدا نیست.
با توجه به گفتههای فوق وظیفه 1 و وظیفه 2 به طور همزمان روی دو هسته مجزا اجرا میشوند؛ با این حال چون ما تنها 3 هسته داریم، هر وظیفه بعدی باید منتظر بماند تا وظیفه جاری کار خود را پایان دهد.
اگر قرار بود وظیفه همزمان دیگری به صف اضافه کنیم، میتوانستیم در نهایت یک هسته داشته باشیم که بین دو وظیفه سوئیچ میکند و این وضعیت منجر به کُندی زیادی در پردازش هر دو وظیفه میشد. بنابراین آگاه باشید که در هر لحظه چندین وظیفه همزمان در حال اجرا دارید.
وظیفه 1 و 3 را میتوان به این صورت در نظر گرفت که به طور سریالی پشت سر هم اجرا میشوند. وظیفه 3 تا زمانی که وظیفه 1 پایان نیافته باشد، آغاز نمیشود. برای نمونه وظیفه 4 میتواند از سوی وظیفه 3 ایجاد شده باشد تا کاری را همزمان با پایان یافتن وظیفه 3 اجرا کند، مثلاً دادهها را از سرور دانلود کند.
وظیفه 5 میتواند یا در صورت پایان نیافتن وظیفه 3 به صورت سریالی ایجاد شود و یا از سوی هر یک از صفهای دیگر در صورتی که وظیفه 3 پایان گرفته باشد، ایجاد شود. در اغلب موارد هنگامی که این الگو را در ابزارهای XCode ملاحظه میکنید، باید بدانید که میتواند به صورت سریالی ایجاد شود.
وظیفه 6 نیز میتواند با هر کدام از صفها ایجاد شود، زیرا متصل است و همه هستهها نیز موجود هستند.
در سطحی عمیقتر که در این مطلب بررسی نکردیم؛ بهینهسازیهایی وجود دارند که پردازنده میتوانند جهت بهبود زمانبندی وظایف حتی در مواردی که سریال یا همزمان هستند مورد استفاده قرار دهند. بنابراین توضیحات ما در خصوص روش ایجاد این وظایف ممکن است با آنچه شما در زمان دیباگ کردن عملکرد اپلیکیشن خود میبینید متفاوت باشد. این توضیحات صرفاً یک مرور سطح بالا از شیوه زمانبندی کارها محسوب میشود.
روشهای زمانبندی کارها
بنابراین در مورد روش اجرای زمانبندی وظایف به قدر کافی صحبت کردیم؛ اما در مورد روش زمانبندی آنها روی صفهای متفاوت هنوز توضیح ندادهایم. ابتدا باید در مورد اولویتبندیهای صفهای مختلف (QoS) صحبت کنیم و سپس کدهای مربوطه را ارائه میکنیم.
User Interactive یا (تعاملی کاربر - نخ اصلی)
این صف اصلی اپلیکیشن شما است و زمانی که منطق برنامهتان را مینویسید همان صف پیشفرض محسوب میشود که جزء نخهای پسزمینه نیست. همه آیتمهای رابط کاربری (UI) در این صف استفاده میشوند، زمانی که کاربر روی یک دکمه ضربه میزند، این کار روی صف اصلی اجرا میشود. کارهایی که در صف اصلی قرار دارند، میبایست بیدرنگ اجرا شوند.
User Initiated (آغاز شده از سوی کاربر)
تعاملهای کاربر اپلیکیشن یکی از ضروریترین اجزای آن محسوب میشود؛ اما در پارهای موارد یک کاربر درخواستی ارائه میکند که پرداز آن به اندکی زمان نیاز دارد. اگر شما این کار را روی صف اصلی اجرا کنید؛ موجب میشود که رابط کاربری قفل شود و کاربر نتواند کار دیگری انجام دهد. این وضعیت بدی است و به جای آن میتوان از اولویت User Initiated برای این وظایف استفاده کرد. در این حالت اپلیکیشن میداند که باید نتایج را در اولین فرصت ممکن بازگشت دهد. برای نمونه این وضعیت در مواردی که در بازی چیزی بهروزرسانی میشود و یا زمانی که دادههایی قرار است از سرور روی نقشه دریافت شود مورد استفاده قرار میگیرد. در این وضعیت کارها تقریباً بیدرنگ اجرا میشوند.
Default (پیشفرض)
این صف پیشفرض است که وظایف پسزمینه به آن انتساب مییابند. این صف اولویت پایینتری نسبت به حالت User Initiated دارد. دلیل آن این است که کاربر این کارها را بیدرنگ یا در آینده نزدیک نمیخواهد. این صف هنگامی مورد استفاده قرار میگیرد که اولویت صف تعیین نشده باشد. کارهایی که در این صف قرار میگیرند، میتوانند از بازه زمانی بیدرنگ تا در طی چند دقیقه اجرا شوند.
Utility (کاربردی)
نام این صف کاملاً گویا است به شرط این که بدانید موارد کاربردی در دنیای برنامهنویسی به کدام موارد گفته میشود. از این صف برای ایجاد درخواستهای شبکه جهت دانلود دادههای غیر ضروری یا ذخیرهسازی دائمی دادهها روی دیسک استفاده میشود. در این صف کارها در طی چند ثانیه تا چند دقیقه اجرا میشوند.
Background (پسزمینه)
این صف برای وظایفی استفاده میشود که به منظور نگهداری (maintenance) اپلیکیشن مورد نیاز هستند. این امور برای شما مهم هستند؛ اما کاربر از آنها اطلاعی ندارد. مثالی از آن اندیسگذاری است. کارهای موجود در این صف ممکن است چند دقیقه طول بکشند.