۳۱ فروردین ۱۴۰۵
با چنلها آشنا میشویم و یاد میگیریم گوروتین ها چطور متوانند با یکدیگر ارتباط برقرار کنند
در گولنگ, گوروتینها میتوانند از طریق channelها مقادیر را به یکدیگر منتقل کنند. یک channel شبیه یک لوله است که یک goroutine میتواند چیزی را داخلش بیندازد و گوروتین دیگری آن را در طرف دیگر دریافت کند.
هر چنل دارای یک دیتا تایپ است که مشخص میکند چه نوع داده ای را میتواند انتقال دهد. برای تعریف یک چنل می توانیم از chan به همراه دیتا تایپی که باید انتقال پیدا کند استفاده کنیم:
برای ارسال و دریافت اطلاعات از چنل از عملگر <- استفاده میکنیم. اگر جهت آن به سمت چنل باشد یک پیام به چنل ارسال میشود:
و اگر خلاف جهت چنل باشند یک پیام از چنل دریافت میشود:
وقتی یک گوروتین میخواهد مقداری را به یک channel ارسال کند یا مقداری از آن دریافت کند، اجرای آن در همان نقطه متوقف یا بلاک میشود تا عملیات تکمیل شود. در هنگام ارسال (messages <- “ping”)، گوروتین فرستنده منتظر میماند تا یک گوروتین دیگر آن مقدار را از چنل دریافت کند.
در هنگام دریافت (<-messages)، گوروتین گیرنده منتظر میماند تا یک مقدار برای خواندن در چنل موجود شود. به عبارت دیگر، چنل نقطهای است که گوروتینها در آن به هم وصل میشوند و اجرای آنها تا زمان تکمیل عملیات متوقف میشود.
این همان مفهوم بلوک شدن (blocking) در زمان ارسال و دریافت است. ارسال و دریافت در یک چنل در واقع یک جفت عملیات همزمان هستند. ارسال بدون دریافت کامل نمیشود (و همچنین بلعکس) و فقط زمانی که عمل دریافت انجام شد، هر دو گوروتین میتوانند به اجرای خود ادامه دهند.
هنگامی که گوروتینها از طریق چنل (channel) با هم ارتباط دارند، یکی از چالشهای مهم این است که چطور پایان ارتباط را اعلام کنیم. سه روش رایج وجود دارد که هرکدام مزایا و معایب خاص خودشان را دارند.
در گولنگ هر نوع از داده دارای یک مقدار صفر (zero value) است. این مقدار وقتی متغیری تعریف میکنیم به آن نسبت داده میشود به عنوان مثال:
یک روش برای اعلام پایان ارتباط ارسال مقدار صفر یا zero value به چنل است.
این روش دارای مزایا و معایبی نسبت به باقی روش هایی است که در ادامه بررسی خواهیم کرد.
این روش به دلیل ریسک بالا معمولا مورد استفاده قرار نمیگیرد.
در این روش یک مقدار خاص مثلا -1 یا END را برای اعلام قطع ارتباط مورد استفاده قرار میدهیم:
این روش نیز در حالتی که هدف تنها پایان ارتباط است مورد استفاده قرار نمیگیرد. روش استاندارد برای اعلام پایان ارتباط, بستن چنل است که در ادامه مورد بررسی قرار میدهیم.
در Go بهترین روش برای اعلام پایان ارتباط این است که چنل را ببندیم.
بعد از بسته شدن چنل در Go، اگر چنل خالی باشد، هر بار خواندن از آن مقدار پیشفرض (zero value) را برمیگرداند؛ برای تشخیص اینکه این مقدار داده واقعی است یا نتیجه بسته شدن چنل، میتوان از مقدار دوم (ok) استفاده کرد که از نوع بولی است و وقتی false باشد نشان میدهد چنل بسته شده و مقدار دریافتشده واقعی نیست.
در ادامه رفتار و جزییات چنل ها را در هنگامی که بسته (close) شوند بررسی خواهیم کرد.
خواندن از channel یعنی دریافت دادهای که توسط یک goroutine دیگر ارسال شده است و این عملیات بهصورت پیشفرض بلاککننده (blocking) است؛ یعنی اگر دادهای در چنل موجود نباشد، goroutine منتظر میماند تا مقداری ارسال شود.
میتوان داده را بههمراه مقدار دومی دریافت کرد که وضعیت باز یا بسته بودن چنل را مشخص میکند.
استفاده از range روی چنل، روشی رایج و خوانا برای دریافت پیوسته دادهها تا زمان بسته شدن آن است.
چنلها در Go بهصورت پیشفرض دوطرفه (bi-directional) هستند بدین معنی که امکان ارسال و دریافت دادهها را فراهم میکنند. میتوان چنلها را بهگونهای تعریف کرد که فقط برای ارسال یا فقط برای دریافت دادهها مورد استفاده قرار گیرند.
چنل فقط فرستنده و فقط گیرنده به شکل های زیر هستند:
با استفاده از چنل های یکطرفه, مثالی که قبلتر داشتیم رو میتوانیم به صورت زیر بازنویسی کنیم:
چنل های یک طرفه (فقط فرستنده و یا فقط گیرنده) مزایا و معایبی دارند.
کامپایلر در Go جلوی استفاده نادرست را میگیرد؛ بهعنوان مثال، اگر چنلی فقط برای دریافت تعریف شده باشد، امکان ارسال داده به آن وجود نخواهد داشت.
نوع چنل بهوضوح نقش هر goroutine را مشخص میکند (فقط فرستنده یا فقط گیرنده).
بسیاری از خطاهای همزمانی، مانند ارسال ناخواسته یا استفاده نادرست از چنل، در همان زمان کامپایل شناسایی میشوند.
وقتی تابعی تنها یک <-chan بهعنوان ورودی میگیرد، تضمین میشود که صرفاً مصرفکننده است و در تولید یا تغییر جریان داده دخالتی ندارد.
چنلهای یکطرفه انعطافپذیری کمتری دارند و نمیتوان از آنها برای هر دو عمل ارسال و دریافت استفاده کرد، حتی اگر بعداً نیاز تغییر کند.
در پروژههای کوچک، ممکن است باعث پیچیدهتر شدن امضای توابع و کاهش خوانایی اولیه شود.
در برخی موارد نیاز به تبدیل بین چنلهای دوطرفه و یکطرفه وجود دارد که میتواند کد را کمی پیچیدهتر کند.
در سناریوهایی با چندین فرستنده یا گیرنده، محدود کردن جهت چنلها ممکن است طراحی را دشوارتر کند.
چنلهای بافر (Buffered Channels) در Go نوعی از چنلها هستند که میتوانند تعدادی مقدار را بدون نیاز به دریافت فوری نگه دارند؛ برخلاف چنلهای معمولی (unbuffered) که ارسال و دریافت باید همزمان انجام شود. چنلهای بافر مشابه یک صف (queue) با ظرفیت مشخص عمل میکنند.
همانطور که در مثال بالا میبینیم هیچ تفاوتی در تعریف چنل ایجاد نمیشود و تنها در هنگام تخصیص حافظه میتوان مشخص کرد که یک چنل بافر شده باشد یا نه.
رفتار چنل بافر شده به صورت زیر است:
چطور متوجه بشیم از چنلهای بافر شده باید استفاده کنیم؟
معمولا کد را میتوان اول بدون استفاده از چنلهای بافر شده نوشت و سپس در صورت نیاز چنل را به بافر شده تبدیل کرد.
به زودی…
nilبه زودی…