برنامه نویسی 528 بازدید

در طی سال‌های اخیر شاهد رشد فزاینده‌ای در علاقه به زبان Go یا GoLang بوده‌ایم. شاید آموختن یک زبان جدید برای هیچ توسعه‌دهنده‌ای موضوع چندان خوشایندی نباشد؛ اما ما در این نوشته قصد داریم شما را قانع بکنیم که چرا باید شروع به یادگیری زبان Go بکنید. ما در این راهنما قصد نداریم روش نوشتن یک پروژه Hello World را به شما آموزش دهیم، چرا که راهنماهای آنلاین بسیار زیادی در این خصوص وجود دارند.

از این رو در این نوشته قصد داریم وضعیت کنونی سخت‌افزار- نرم‌افزار رایانه و دلیل نیاز شما به یک زبان جدید مانند Go را توضیح دهیم؛ چون اگر مسئله‌ای نباشد، راه‌حلی نیز نخواهد بود.

محدودیت‌های سخت‌افزار

«قانون مور» (Moore’s law) در حال زوال است. نخستین پردازنده پنتیوم 4 با سرعت کلاک 3.0 گیگاهرتز در سال 2004 از سوی اینتل معرفی شده است. امروزه مک‌بوک‌های مدرن پردازنده‌ای با سرعت 2.9 گیگاهرتز دارند، از این رو می‌بینیم که در طی نزدیک به 15 سال شاهد رشد چندانی در زمینه توان پردازشی نبوده‌ایم. نمودار مقایسه افزایش توان پردازش در طی زمان را در نمودار زیر مشاهده می‌کنید:

از روی نمودار فوق مشخص است که عملکرد تک-نخی (single-thread) و فرکانس پردازنده‌ها برای بیش از یک دهه ثابت مانده است. اگر فکر می‌کنید که افزودن ترانزیستورهای بیشتر راه‌حل کار است در این صورت در اشتباه هستید، چون در مقیاس‌های کوچک‌تر از این، برخی خصوصیات کوانتومی (مانند tunneling) ظاهر می‌شوند و از این رو جای دادن ترانزیستورهای بیشتر به هزینه بالاتری نیاز دارد و تعداد ترانزیستورهایی که می‌توان به ازای هر واحد از هزینه روی تراشه قرار داد کاهش می‌یابند.

بنابراین روش‌های زیر به عنوان راه‌حل مسئله فوق استفاده شده‌اند:

  • سازندگان شروع به افزودن هسته‌های بیشتر به تراشه‌ها کرده‌اند. امروزه ما با پردازنده‌های چهار هسته‌ای و هشت هسته‌ای مواجه هستیم.
  • از روش hyper-threading استفاده می‌شود.
  • همچنین کش بیشتری به پردازنده اضافه می‌شود تا عملکرد آن افزایش یابد.

اما راه‌حل‌های فوق، محدودیت‌های خاص خود را نیز دارند. ما نمی‌توانیم کش بیشتری را برای ارتقای توان پردازنده به آن اضافه کنیم، چون کش محدودیت‌های فیزیکی دارد و هر چه بزرگ‌تر شود سرعت آن کاهش می‌یابد. افزودن هسته‌های بیشتر نیز هزینه‌های خاص خود را دارد و علاوه بر آن نمی‌تواند نسبت به مقیاس بی‌تفاوت باشد. این پردازنده‌های چندهسته‌ای می‌توانند به طور همزمان چندین هسته را به کار بگیرند و بدین ترتیب بحث همزمانی پیش می‌آید که در ادامه در مورد آن بیشتر توضیح خواهیم داد.

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

پردازنده‌های مدرن مانند خودروهای اسباب‌بازی مجهز به سوخت نیترو هستند. آن‌ها تنها در مسافت‌های اندک عملکرد مناسبی دارند. متأسفانه زبان‌های مدرن برنامه‌نویسی دارای زوائد و حواشی زیادی هستند.

Go دارای Goroutine است

همان طور که قبل‌تر اشاره کردیم، سازندگان سخت‌افزار هر چه در توان دارند، هسته‌های بیشتری به پردازنده اضافه می‌کنند تا عملکرد آن‌ها را افزایش دهند. همه مراکز داده (Data centers) بر مبنای این پردازنده‌ها عمل می‌کنند و می‌توانیم منتظر افزایش تعداد هسته‌ها در سال‌های آتی نیز باشیم. علاوه بر آن اپلیکیشن‌های امروزی از میکروسرویس‌های چندگانه برای حفظ اتصال‌های پایگاه داده، صف‌های پیام و نگهداری کش استفاده می‌کنند. بنابراین نرم‌افزاری که توسعه می‌دهیم و زبان‌های برنامه‌نویسی می‌بایست از «همزمانی» (Concurrency) پشتیبانی کنند و قابل مقیاس‌پذیری با افزایش تعداد هسته‌های پردازنده‌ها باشند.

اما اغلب زبان‌های مدرن برنامه‌نویسی مانند جاوا، پایتون و غیره در محیط تک نخی دهه 90 ایجاد شده‌اند. اغلب این زبان‌ها از چند نخی پشتیبانی می‌کنند؛ اما مسئله اصلی به اجرای همزمان، «قفل نخ بندی» (threading-locking)، شرایط رقابت و بن‌بست‌ها مربوط است. این چیزها امکان ایجاد اپلیکیشن‌های چند نخی در زبان‌ها را فراهم می‌سازد.

به عنوان نمونه ایجاد یک نخ جدید در جاوا چندان از نظر حافظه بهینه نیست. از آن جا که هر نخ نزدیک به 1 مگابایت حافظه هیپ مصرف می‌کند، اگر در نهایت هزاران نخ ایجاد کنید، فشار زیادی روی حافظه هیپ وارد می‌آید و موجب از کار افتادن سیستم به دلیل کمبود حافظه می‌شود. ضمناً اگر بخواهید بین دو یا چند نخ ارتباط برقرار کنید، کار دشواری خواهد بود.

از سوی دیگر Go در سال 2009 منتشر شده است یعنی زمانی که پردازنده‌های چند نخی ارائه شده بود. به همین دلیل Go با ذهنیت چند نخی طراحی شده است و به همین دلیل نیز Go به جای نخ دارای «Goroutine» یا «رویه‌های Go» است. این رویه‌ها 2 کیلوبایت حافظه هیپ مصرف می‌کنند و از این رو می‌توان میلیون‌ها goroutine به صورت همزمان داشت:

مزیت‌های دیگر به صورت زیر هستند:

  • Goroutine-ها پشته‌های قطعه‌بندی شده‌ای دارند. یعنی تنها در حالت نیاز، از حافظه اضافی استفاده می‌کنند.
  • Goroutine-ها زمان آغازین سریع‌تری نسبت به نخ‌ها دارند.
  • Goroutine-ها دارای رویه‌های ابتدایی برای ارتباط امن (کانال) بین خودشان هستند.
  • Goroutine-ها امکان  رفع وضعیت قفل متقابل، هنگام اشتراک ساختمان های داده را فراهم می‌سازند.
  • ضمناً Goroutine-ها و نخ‌های سیستم عامل نگاشت 1:1 ندارند. یک Goroutine منفرد می‌تواند روی نخ‌های چندگانه اجرا شود. Goroutine ها می‌توانند به تعداد معدودی از نخ‌های سیستم عامل تقسیم شوند.

همه نکات فوق باعث می‌شوند که Go برای مدیریت همزمانی‌ها مانند جاوا، C و ++C بسیار قدرتمند باشد؛ در حالی که کد اجرای همزمان را نیز مانند Erlang سرراست و زیبا نگه می‌دارد.

Go مزیت‌های هر دو جنبه را دارد، نوشتن همزمانی آسان است و مدیریت آن نیز کارآمد است.

Go به طور مستقیم روی سخت‌افزار اجرا می‌شود

یکی از مزیت‌های قابل توجه استفاده از C و ++C نسبت به زبان‌های مدرن‌تر سطح بالا مانند جاوا/پایتون، عملکرد بالاتر آن‌ها است، چون ++C/C کامپایل می‌شود و یک زبان تفسیری نیست.

پردازنده‌ها فایل‌های باینری را درک می‌کنند. به طور کلی زمانی که یک اپلیکیشن را با استفاده از جاوا یا دیگر زبان‌های مبتنی بر JVM می‌سازید، در هنگام کامپایل کردن پروژه، کد قابل خواندن از سوی انسان را به صورت «بایت-کد» (byte-code) درمی‌آورد که JVM یا دیگر ماشین‌های مجازی که روی سیستم عامل میزبان اجرا می‌شوند، می‌توانند درک کنند. JVM در زمان اجرا، این بایت‌کدها را تفسیر می‌کند و آن‌ها را به فایل‌های باینری تبدیل می‌کند که پردازنده می‌تواند درک کند:

مراحل اجرا برای زبان‌های مبتنی بر JVM

اما در سوی دیگر C و ++C روی ماشین‌های مجازی اجرا نمی‌شوند و بدین ترتیب یک مرحله از چرخه اجرا کم می‌شود و عملکرد ارتقا می‌یابد. در این حالت کد قابل خواندن از سوی انسان مستقیماً به کدهای باینری کامپایل می‌شود.

اما آزاد کردن و تخصیص متغیر در این زبان‌ها بسیار دشوار است. با این حال اغلب زبان‌های برنامه‌نویسی تخصیص و حذف اشیا را با استفاده از Garbage Collector و الگوریتم‌های «شمارش ارجاع» (Reference Counting) مدیریت می‌کنند.

Go مزیت‌های هر دو مورد فوق را دارد، به دلیل این که مانند زبان‌های سطح پایین چون C یا ++C یک زبان کامپایل شونده است. این بدان معنی است که عملکرد تقریباً شبیه زبان‌های سطح پایین است و از طرف دیگر از garbage collection برای تخصیص و آزادسازی شی‌ءها استفاده می‌کند. از این رو دیگر به گزاره‌های ()malloc و ()free نیازی ندارید.

نگهداری کدهای نوشته شده به زبان Go آسان است

واقعیت این است که Go داری ساختار برنامه‌نویسی پیچیده‌ای مانند دیگر زبان‌های برنامه‌نویسی نیست. ساختار آن کاملاً سرراست و ساده است.

طراحان Go در گوگل با همین ذهنیت این زبان را خلق کرده‌اند. از آنجا که گوگل دارای codebase بزرگی است و هزاران توسعه‌دهنده به طور همزمان روی این codebase کار می‌کنند، بنابراین درک کد باید برای توسعه‌دهندگان دیگر ساده باشد و یک قطعه کد باید بدون کمترین اثر جانبی روی قطعه کد دیگر اجرا شود. همین نکته باعث خواهد شد که کد به سادگی قابل نگهداری بوده و اصلاح آن آسان باشد.

Go عامدانه بسیاری از ویژگی‌های زبان‌های شیءگرای مدرن را کنار گذاشته است.

کلاس وجود ندارد: همه چیز در GO در «بسته‌ها» (Packages) خلاصه می‌شود. Go به جای کلاس‌ها صرفاً از struct ها استفاده می‌کند.

از وراثت پشتیبانی نمی‌کند: این امر موجب سهولت اصلاح کد می‌شود. در زبان‌های دیگر مانند جاوا/ پایتون، در صورتی که کلاس ABC، کلاس XYZ را به ارث ببرد و تغییراتی در XYZ صورت بگیرد در آن صورت ممکن است اثرات جانبی روی کلاس‌هایی که از XYZ به ارث می‌رسند ایجاد شود. Go با حذف وراثت، موجب سهولت درک کد نیز شده است چون دیگر برای درک طرز کار کد نیازی به بررسی سوپرکلاس آن وجود ندارد.

برخی از موارد مهم قابل ذکر دیگر در این زمینه عبارت هستند از:

  • «سازنده» (constructors) وجود ندارد.
  • Annotation وجود ندارد.
  • Generics وجود ندارد.
  • Exception وجود ندارد.

تغییرات فوق موجب شده است که Go از دیگر زبان‌ها بسیار متفاوت باشد و همچنین برنامه‌نویسی به این زبان با برنامه‌نویسی در زبان‌های دیگر اختلاف داشته باشد. ممکن است برخی افراد این تغییرات را دوست نداشته باشند؛ اما این بدان معنی نیست که بدون وجود ویژگی‌های فوق نمی‌توانید اپلیکیشن خود را کدنویسی کنید. همه آن چیزی که نیاز دارید، این است که 2 یا 3 خط کد بیشتر بنویسید. اما اگر به سمت مثبت قضیه توجه کنیم، موجب می‌شود که کد شما تمیزتر باشد و شفافیت بیشتری به کد اضافه شود.

خوانایی یا کارآمدی کد
خوانایی یا کارآمدی کد

نمودار فوق نشان می‌دهد که Go تقریباً به اندازه ++C و C کارآمد است، در حالی که ساختار کد به اندازه روبی یا پایتون و دیگر زبان‌ها ساده است. این همان نکته موفقیت Go هم برای انسان‌ها و هم پردازنده‌ها است.ساختار Go برخلاف دیگر زبان‌های جدید مانند Swift بسیار پایدار است. ساختار این زبان از زمان انتشار نسخه 1.0 در سال 2012 همچنان ثابت مانده است و به همین دلیل دارای تطبیق‌پذیری رو به عقب است.

Go از سوی گوگل پشتیبانی می‌شود

البته شاید این یک مزیت مستقیم محسوب نشود، اما Go از سوی گوگل طراحی شده و پشتیبانی می‌شود. گوگل یکی از بزرگ‌ترین زیرساخت‌های ابری دنیا را دارد و مقیاس‌پذیری آن بسیار عظیم است. Go از سوی گوگل به منظور حل مشکلات آن‌ها در پشتیبانی از مقیاس‌پذیری و کارایی طراحی شده است. این‌ها همان مسائلی هستند که همه افراد هنگام ایجاد سرورهای شخصی خود با آن مواجه می‌شوند.

همچنین کارهای زیادی از سوی شرکت‌های بزرگی مانند اینتل، IBM، ادوبی و حتی Medium در مورد Go صورت گرفته است.

سخن پایانی

با این که Go از دیگر زبان‌های برنامه‌نویسی بسیار متفاوت است؛ اما همچنان مفاهیم مشترک زیادی با آن‌ها دارد. Go مانند C یا ++C دارای عملکرد بالایی است و مانند جاوا مدیریت همزمانی بسیار کارآمدی دارد و کدنویسی با آن نیز به اندازه پایتون یا پرل جذاب است.

واقعیت است که محدودیت‌های سخت‌افزاری باعث می‌شود که ما مجبور باشیم از Go استفاده کنیم. توسعه دهنگان نیاز دارند که سخت‌افزار را درک کنند و برنامه خود را بر همین مبنا بهینه‌سازی نمایند. نرم‌افزار بهینه‌شده می‌تواند روی سخت‌افزار ارزان‌تر و کندتری (مانند دستگاه‌های اینترنت اشیا) نیز اجرا شود و به طور کلی تجربه کاربری بهتری برای کاربر نهایی رقم می‌زند.

اگر این مطلب برایتان مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای 1 نفر

آیا این مطلب برای شما مفید بود؟

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *