همروندی در زبان گو

در این مقاله میخوایم در مورد همروندی صحبت کنیم، هر جا از این واژه استفاده شده منظور همان Concurrency است.

همروندی در زبان گولنگ یکی از مهمترین ویژگی های این زبان به حساب می آید، اما همروندی در این زبان یک راه تفکر است بیشتر تا یک Syntax.

برای اینکه به قدرت زبان گو پی ببرید لازم است اول شما روش این زبان را برای کدهایی که بصورت همزمان اجرا میشوند را بررسی کنید.

مدلی که زبان گو از آن در همروندی استفاده میکند CSP نام دارد که مخفف Communicating sequential proccesses یا به عبارتی ارتباط متوالی فرآیندها با یکدیگر که در علم پایه کامپیوتر به این مدل ارتباط بین سیستم های همروند میگویند.

اما ما اینجا نیستیم که در مورد علم و دانش صحبت کنیم که !!! بنابراین میریم سراغ موارد عملی و جذاب.

یه جمله ی معروفی هستش که میگه :

Do not communicate by sharing memory; instead, share memory by communicating

خوب این واقعا یعنی چی؟ برای خود من مدت های زیادی طول کشید تا اینو کامل درکش کنم ولی وقتی درکش کردم کارکردن با گولنگ برام خیلی خیلی روان تر شد. آلبرت انیشتن میگه اگر شما نمیتونید خیلی ساده توضیح بدین چیزیرو، شما اونو خیلی خوب متوجه نشدینش! بنابراین تعریف ساده تر اون جمله میشه:

Do not communicate by sharing memory

به عنوان یک برنامه نویس وقتی در مورد همروندی فکر میکنید و نحوه ی اجرای کد اون، بیشترین چیزی که به ذهنتون میرسه تعداد خیلی زیادی از thread هستش که بصورت همزمان دارن بصورت خیلی پیچیده اجرا میشن.بنابراین اکثر مواقع ناچار به اشتراک گذاری دیتا هستین مثل structures, variable, memory و هر چیزی که فکرشو میکنین به thread های مختلف.

شما اینکارو انجام میدین با قفل کردن بخشی از مموری که 2 threads نمیتونن همزمان یا بنویسن و یا دسترسی داشته باشند و یا اینکه به امان خدا ولش میکنین تا آزاد بشه و بهترین حالت شاید پیش بیاد و این همون مشکلیه که خیلی از زبانی محبوب باهاش درگیر هستند و باعث اتفاق های عجیبی مثل race condition, memory mangement و اتفاقات غیرقابل پیش بینی که هر شب ممکنه شمارو از خواب بیدار کنه.

Instead, share memory by communicating

خوب گو چطوری این کارو انجام میده ؟

بجای قفل کردن متغیرها برای اشتراک گذاری مموری، گو به شما این اجازه میده که متغیری را از یک thread به یک thread دیگر ارسال کنید (در حقیقت دقیقا یک thread نیست اما فعلا بیایم اونو یک thread درنظر بگیریم)

رفتارپیش فرض برای thread ارسال کننده و همچنین thread دریافت کننده اینه که هر دو صبر میکنند تا value ارسال شده به مقصدش برسه. نکته اینجاست که در زمانی که این دو thread صبر کرده اند باعث میشه یک همگام سازی مناسبی بین thread ها بوجود بیاد.

چه مزیتی به ما میده این موضوع؟

در واقع شانس خیلی خیلی کمتری وجود داره که مواردی مثل race conditions بخواد پیش بیاد چون هر دو صبر میکنند تا value کاملا ارسال بشه.

گولنگ این امکانات بصورت Native و built-in به شما ارایه میکند بدون نصب هیچگونه فریمورک و یا کتابخانه ای خاص.

گولنگ همچنین به شما ویژگی دیگه ای هم میده تحت عنوان buffered channel که در بعضی موارد شما نمیخواینهر دو thread قفل و سینک بشن تا زمانی که value ارسال بشه. و فقط زمانی locking اتفاق بی افته که شما تعداد خاصی تعریف کرده باشین برای صبر کردن این thread ها

نوشتن کد همروند در گولنگ

خوب چطوری ما میخوایم با روش share by communication model کد خودمونو بنویسیم ؟

در گو، یک گو روتین (Goroutine) با خودش این مفهومی که ما دنبالش میگردیمو برای ما میاره !

درواقع، اون یک thread نیست، اون یک function هستش که میتونه بصورت همروند با بقیه گوروتین ها در یک فضای مشترک اجرا بشه. تعداد اون ها زیاده در لایه OS و اگر یکی از اون ها قفل بشه بقیه به کار خودشون ادامه میدن. تمام این مراحل سینک کردن و مدیریت مموری در خود زبان داره مدیریت میشه بصورت Native. اینکه میگیم اون ها thread نیستن چون لزوما بصورت پارالل اجرا نمیشن

برای استارت یک گو روتین کافیه از کلمه ی کلیدی "go" استفاده کنین.


خط بالا یک گوروتین را که فانکشن ()start اجرا میکنهوجود داره. برنامه اول چاپ میکنه started توجه کنید که خطی که ما چاپ کردیم started بعد از جایی که گوروتین اجرا شده این به ما این نکته نشون میده که بعد از اینکه گوروتین استارت شده برنامه اجراش به خط بعدی رفته. ما یک تایم اوت ست کردیم برای گو روتین خودمون و بعد از رسیدن به اون تایم میبینیم که اجرا میشه و نهایتا finished چاپ میشه

حالا چه اتفاقی می افته اگر ما تایم اوت حذف کنیم ؟


اصلا چاپ نشد ! چرا ؟به دلیل اینکه گوروتین اصلی اجراش تموم میشه تا بخواد اون یکی برای خودش برنامه ریزی بکنه.

Main Goroutine

فانکشن main در پکیج main یک گوروتین اصلی هستش. تمای گوروتین ها شروع میشن از همین main goroutine این گوروتین ها میتوانند چندین گوروتین دیگر را استارت بزنند.

گوروتین اصلی بیانگر برنامه اصلی است. وقتی خارج میشه یعنی کل اپلیکیشن خارج شده.

گوروتین ها والد و یا فرزندی ندارند. وقتی شما یک گوروتین را استارت میزنید اون کنار بقیه گوروتین ها اجرا میشه.

هر گوروتین زمانی تمام میشود که فانکشن اون چیزی را برگرداند. تنها مورد استثنا اینه که تمامی گوروتین ها استاپ میشوند وقتیکه گوروتین اصلی استاپ یا خارج شود.

Creating Multiple Goroutines


برنامه بالا در یک لوپ ۱۰ گوروتین را اجرا میکند و هروقتیکه شما این برنامه را اجرا کنید به شما نتیجه ای مختلف میدهد از اونجایی که گوروتین ها بصورت همروند دارند اجرا میشوند نتیجه قابل انتظاری نمیشود دید.