<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>پست‌های انتشارات Pleasure of Golang Programming</title>
        <link>https://virgool.io/pogopr/feed</link>
        <description>جایی برای اشتراک لذت برنامه نویسی به زبان Go</description>
        <language>fa</language>
        <pubDate>2026-06-16 12:32:26</pubDate>
        <image>
            <url>https://files.virgool.io/upload/publication/om661mhsm0qs/7dyhcm.png</url>
            <title>Pleasure of Golang Programming</title>
            <link>https://virgool.io/pogopr</link>
        </image>

                    <item>
                <title>الگوهای concurrency (بخش ۲): cancellation و context</title>
                <link>https://virgool.io/pogopr/%D8%A7%D9%84%DA%AF%D9%88%D9%87%D8%A7%DB%8C-concurrency-%D8%A8%D8%AE%D8%B4-%DB%B2-cancellation-%D9%88-context-djiy153qy9dl</link>
                <description>در مقاله اول از مجموعه مقالات در ارتباط با الگوهای concurrency به یکی از ساده ترین در عین حال یکی از پرکاربردترین الگوها به نام for-select پرداختیم و بیان کردیم که چگونه از این الگو برای انتظار در انجام یا عدم انجام وظیفه ای خاص استفاده نماییم. مطرح کردیم که چگونه در واقعیت این مورد متفاوت از پیاده سازی الگوریتمیک آن است. در این مقاله قصد داریم تا به قطعه دیگری از پازل الگوهای concurrency پرداخته و این سوالات را مطرح کنیم که چگونه می بایست یک goroutine را متوقف نماییم؟ آیا امکان memory leak در عدم توقف goroutine ها وجود دارد؟ راه حل پیشنهادی در زبان Go با استفاده از standard packages چیست؟ برای درک بهتر از شیوه برخورد با این پارادایم فکری مطالعه و مرور سریع اولین مقاله پیشنهاد می شود.شروع با طرح چند سوالمطلب را با طرح چند سوال آغاز می کنیم. سوال اول: آیا نیاز به کنترل میزان حافظه مصرفی از جانب goroutine ها هستیم؟ سوال دوم: احتمال memory leak در اجرای goroutine ها وجود دارد؟پاسخ به این سوالات ساده است. هرچند که میزان استفاده منابع برای اجرای goroutine ها بسیار کمتر از Thread هاست و عملیات context switching در لایه runtime در این زبان اتفاق می افتد، ولی باز هم منابعی را به خود تخصیص خواهد داد و این عمل بدون هزینه نیست. نکته حایز اهمیت در مورد goroutine ها در این است که آنها توسط runtime هیچگاه garbage collect نمی شوند. لذا همواره می بایست نگران مصرف حافظه از جانب goroutine ها باشیم (یکی از دلایل کنترل میزان حافظه از جانب goroutine ها و محدود سازی تعداد آنها الگوی concurrency با عنوان pooling است که در مورد آن در مقاله ای در آینده صحبت خواهیم کرد). در مورد سوال دوم نیز بیان این مطلب لازم است که، بله امکان memory leak وجود دارد. ممکن است یک goroutine به دلیل کد نویسی بعضا اشتباه هیچگاه از حافظه پاک نشود. سوالی که ممکن است مطرح شود که چگونه باید از حذف goroutine از حافظه اطمینان حاصل نماییم. یا به بیان ساده تر، چگونه مطمین شویم که یک goroutine خاتمه پذیر است؟ قاعدتا در هنگام خطا یا در هنگام خاتمه عملیات یک goroutine، حتما خاتمه می یابد. حال سوال اینجاست که در سایر موارد چگونه است؟ پاسخ در اصلی مهم در استفاده از goroutine هاست.اصل مهم در Goroutine هاپاسخ به آخرین سوال مطرح شده در بالا را با بیان اصلی مهم در کاربری goroutine ها آغاز می کنیم.اگر goroutine ای وظیفه ساخت goroutine دیگری را  داشته باشد، وظیفه مدیریت آن goroutine شامل امکان توقف آن goroutine را می بایست داشته باشد.در ساده ترین حالت ممکن می توان این اصل را اینگونه تشریح کرد که ساخت هر goroutine همواره با استفاده از یک تابع رخ خواهد داد و این تابع عموما توسط تابعی دیگر در یک pipeline نمونه سازی و اجرا خواهد شد. اگر تابع فراخواننده را تابه والد (parent) و تابع فراخوانده شده را فرزند (child) بنامیم، نیازمند مکانیزمی هستیم که عمل مدیریت و متوقف سازی child توسط parent رخ دهد. در ساده ترین نوع پیاده سازی در زبان های برنامه نویسی مانند C و Go این روش با ارسال پارامترهای کنترلی، به صورت اشاره گر، از تابع والد به فرزند انجام می شود که نیازمند رفتار خاصی در تابع فرزند، حین وجود مقادیری خاص در این پارامترهای کنترلی است. به این مکانیزم در ساده ترین حالت ممکن signaling گفته می شود. نکته مهم در مورد سیگنال دهی این است که این سیگنال تنها جنبه اطلاع رسانی در وقوع یک رخداد را داشته و معنی دیگری ندارد.عملیات سیگنال دهی از والد به فرزندحال که مفهوم سیگنال دهی را می دانیم، شیوه پیاده سازی کارآمد آن در زبان برنامه نویسی Go به منظور پیاده سازی مکانیزم توقف goroutine فرزند توسط goroutine والد چگونه خواهد بود؟از آنجا که این عمل تنها نیازمند اطلاع رسانی از جانب والد به فرزند خواهد بود بهترین شیوه پیاده سازی پیشنهادی برای نیل به این هدف استفاده از یک channel ارتباطی با عنوان done به منظور سیگنال رسانی می باشد. از آنجا که هم اکنون نیازمند ارسال اطلاعات خاصی از طریق این channel نیستیم به راحتی می توانیم از نوع داده ای {}struct با قابلیت zero space برای پیاده سازی آن استفاده نماییم. همچنین استفاده از یک chan با صورت read only در پیاده سازی done در اصطلاح idiomatic و مرسوم است (در تصویر زیر done به صورت پارامتری read only تعریف شده است شده است). تصویر زیر نمونه ی سطح بالاتری از این مکانیزم را نمایش می دهد:نمونه سطح بالاتری از پیاده سازی done channelنحوه برخورد در childحال باید به این سوال پاسخ داد که، نحوه برخورد و واکنش به سیگنال ارسالی از جانب والد به فرزند می بایست چگونه باشد؟ یا به بیان ساده تر چه عملیاتی می بایست در فرزند در برخورد با done پیاده سازی شود؟ پاسخ ساده است. بستگی دارد :). در اکثر مواقع این ارتباطات والد و فرزندی در قالب چندین گام یا در اصطلاح گرافی از فراخوانی goroutine ها و در قالب pipeline پیاده سازی می شود و این ارتباطات عموما به شکل زنجیره ای از channel ها رخ می دهد. لذا در زمانی که در انتظار دریافت مقادیر از آن channel ها هستیم از الگوی for-select می توانیم استفاده نماییم.به عنوان نمونه تکه کد زیر را مشاهده نمایید:نمونه ای ساده از پیاده سازی for-select در تابع فرزنددر نمونه کد بالا فرض بر این بوده است که در یک مرحله از pipeline داده از طریق val دریافت شده و پس از اعمال برخی عملیات (در بالا به صورت کامنت نمایش داده شده) داده را توسط stream به step بعدی در pipeline ارسال نمایید. در بدنه goroutine از الگوی for-select استفاده شده است که در صورتی که هریک از مقادیر val یا done از جانب goroutine والد مشخص و ارسال شده بود تکه کد مرتبط با آن اجرا گردد. نکته مهم در مورد done در این است که به سادگی تنها return برگردانده شده که این کار باعث اجرای دستور بستن stream خواهد شد. با بسته شدن stream قاعدتا این سیگنال به goroutine های پایین تر در گراف pipeline ارسال شده و آنها نیز متناسب با بسته شدن stream تصمیم مقتضی را خواهند گرفت. نکته مهم در این مورد این خواهد بود که رفتار توابع child بعدی در گام ها برای channel بسته شده و مقادیر واقعی می بایست متفاوت باشد. به خصوص در زمانی که نوع داده channel ورودی و خروجی عدد صحیح مانند int int32 int64 است. در تصویر بالا این رویکرد پیاده سازی نشده است و برخورد با channel بسته در دستیابی به مقدار val به شما واگذار می شود.کاربرد های الگوی cancellationاز جمله مهمترین دلایل نیاز به الگوی cancellation جلوگیری از ادامه غیر ضروری یک فرآیند است. یکی از مصداق های این مورد Timeout است. به عنوان نمونه نمی خواهیم بیش از یک مقدار زمان مشخص، برای پاسخگویی به کاربر تخصیص دهیم. در این زمان این الگو بسیار کارآمد خواهد بود. این مورد در کاربرد درخواست های HTTP به وفور یافت می شود. به عنوان یک نمونه تجربه خوب همواره پیشنهاد می شود تا درخواست های HTTP در سمت server همیشه با timeout همراه باشد. این الگو تا حد بسیار زیادی در استفاده از منابع اعم از CPU و RAM تاثیرگذار است. به عنوان نمونه ای از پیاده سازی مبتدی از timeout تکه کد زیر کمک کننده خواهد بود:نمونه ای از برخورد با doneنمونه کد بالا پیاده سازی درستی از روش timeout در HTTP نیست. تنها عاملی کمک کننده است برای شیوه برخورد با done در تابع والد. در تکه کد فوق، بعد از دو ثانیه در صورتی که مقداری در terminate آماده خواندن نشده باشد، done بسته خواهد شد. همین رویکرد می تواند در درخواست ای HTTP وجود داشته باشد با این تفاوت که احتمالا نتیجه یک chan از controller با done می بایست ترکیب شود، که بهترین شیوه پیاده سازی آن، استفاده از select برای دستیابی به مقادیر از chan حاصل از controller و مقداردهی به  done در هنگامی که  time.After مقداری در channel خروجی خود قرار داد، می باشد.راه حل پیشنهادی استاندارداستفاده از الگوی cancellation و استفاده از done channel راه حلی مناسب برای عملیات سیگنال دهی بین goroutine های فرزند و والد می باشد. با این وجود این الگو دارای نقاط ضعف (به بیان بهتر، دارای کمبودهایی) است. از عمده ترین نقاط ضعف استفاده از done channel این است که چگونه به غیر از سیگنال رسانی اطلاعات دیگری در اختیار goroutine فرزند و والد قرار دهیم؟ یا اینکه چگونه در هر گام از یک pipeline رفتاری منحصر به فرد متناسب با آن مرحله انجام دهیم. به عنوان مثال چگونه در یک مرحله رفتار timeout و در مرحله دیگر deadline داشته باشیم؟ از نسخه 1.7 از زبان Go به صورت استاندارد context package به مجموعه پکیج های استاندارد اضافه شد که این مکانیزم را به نحو شایسته تری پیاده سازی و اجرا می نماید. در ادامه به شرح و چگونگی استفاده از context برای اهداف خود می پردازیم.استفاده از context packageبرای استفاده از context package به جای ارسال done channel به عنوان اولین پارامتر در توابع درون pipeline از نوع داده ای به نام Context استفاده می شود. این نوع داده ای بسیار شبیه به done عمل خواهد کرد. با مشاهده Context متوجه ساختار ساده آن خواهید شد که دارای چهار تابع است. در زمانی که قصد سیگنال دهی برای خاتمه یا کنسل  Context را داشته باشیم از Done استفاده می کنیم. Err در صورتی که Done و کنسل رخ داده باشد دلیل آن را مشخص خواهد کرد. Deadline زمانی که این Context کنسل خواهد شد را تعیین میکند، البته در صورتی که برای وی زمان deadline را تعیین کرده باشیم. به منظور ارسال اطلاعات جانبی و اضافی در pipeline نیز می توان از Value استفاده نمود که عملیات پاس دادن اطلاعات مابین goroutine ها را تسهیل می کند.نوع context.Contextبرای استفاده از context در یک ساختار سلسله مراتبی از توابع goroutine در pipeline نیازمند این هستیم که در ابتدا نمونه ای از Context ساخته شود. برای این منظور دو تابع برای ساخت یک Context وجود دارد که به ترتیب Background و TODO نام دارند. نکته مهم در این است که در production همواره از Background استفاده می شود و از TODO تنها زمانی که هنوز از مراحل کامل در pipeline تصویر دقیقی نداریم، یا در pipeline مراحل بالادستی هنوز تکمیل نشده اند، استفاده می شود. در هر مرحله از اجرای یک pipeline می بایست نمونه Context ساخته شده را به لایه پایین تر پاس دهیم. در صورتی که در هر مرحله خواهان تغییر در شیوه برخورد در Context هستیم می بایست از یکی از توابع زیر استفاده نموده و نمونه حاصل از خروجی آن تابع را به لایه های پایین تر ارسال نماییم. توابع مرتبط با context manipulationشرح عملیات هر کدام ار توابع بالا بسیار ساده است. تنها دو نکته حایز اهمیت است که می بایست در مورد آن دقت شود. نکته اول اینکه همه توابع به عنوان اولین پارامتر ورودی context والد خود را دریافت می کنند (دلیل این دریافت، انتقال اطلاعات به شیوه pass by value به صورت پیش فرض در ساختارها در Go است) و در نهایت نمونه Context جدیدی به عنوان خروجی اول در اختیار قرار می دهند. نکته مهم دیگر این است که تابع cancel در خروجی توابع هنگامی که فراخوانی شود عملیات cancellation برای آن نوع تابع رخ می دهد. به عبارت دیگر done channel آن Context بسته می شود.به عنوان نمونه جهت بیشتر روشن شدن شیوه استفاده تکه کد زیر را در نظر بگیرید:نمونه ای از استفاده از context Timeoutنمونه کد فوق یک pipeline در دو گام را نمایش می دهد که  تابع generator آن در بدنه main نوشته شده است و گام بعدی به شکل تابعی با نام step1 توسعه داده شده است. در کد در بدنه main یک نمونه از context به صورت timeout قرار دارد که قرار است عملیات های بیش از یک ثانیه را کنسل نماید. در step1 نیز عملیاتی توسط time.After شبیه سازی شده است که دو ثانیه زمان به خود تخصیص خواهد داد. واضح است به دلیل اینکه حداکثر زمان یک ثانیه برای اجرای این pipeline تعبیه شده است همواره ctx.Done زودتر مقدار برای خواندن خواهد داشت و دو دستور Println در خروجی استاندارد چاپ خواهد شد.استفاده از دو تابع WithDeadline و WithCancel نیز بر همین اصول استوار است. در مورد ارسال و دریافت اطلاعات جانبی از جانب والد به فرزندان نیز استفاده از تابع WithValue بسیار ساده است. تنها نکته حایز اهمیت این خواهد بود که برای دریافت اطلاعات در فرزندان تابع Value از Context می بایست مورد استفاده قرار گیرد. نمونه ای ساده از استفاده از آن تابع در تکه کد زیر نمایش داده شده است:نمونه ای از استفاده از WithValueخاتمهامیدوارم از مطالعه این مقاله مختصر لذت برده باشید و در ذهن خود پاسخ به این سوال را داده باشید که چرا یکسری از توابع از کتابخانه های مختلف (مثلا  mongo driver) دارای پارامتر ورودی و ابتدایی Context در خود هستند. منتظر شنیدن نظرات شما برای رفع نواقص و بهبود این مقاله هستم.</description>
                <category>Pleasure of Golang Programming</category>
                <author>علی فرهمند</author>
                <pubDate>Mon, 10 Aug 2020 18:19:10 +0430</pubDate>
            </item>
                    <item>
                <title>الگوهای concurrency (بخش ۱):  for-select</title>
                <link>https://virgool.io/pogopr/%D8%A7%D9%84%DA%AF%D9%88%D9%87%D8%A7%DB%8C-concurrency-%D8%A8%D8%AE%D8%B4-%DB%B1-for-select-xunzl7ih944t</link>
                <description>نمونه ای از الگوهای همروند در اجرای worker ها توسط fan outهمزمانی و همروندی یک راه حل برای عدم تطابق قدرت پردازنده ها با قانون ارایه شده توسط موور (Moore Law) است. آنچه که باعث شده تا طراحان پردازنده ها به فکر استفاده از چندین هسته در پردازنده ها یا بکارگیری چندین پردازنده در کامپیوتر ها باشند. یک قانون ساده وجود دارد: همواره مسایلی مطرح می شوند که نیازمند توان محاسباتی، فراتر از قدرت کنونی پردازنده هاست که در پیاده سازی و اجرا توسط یک پردازنده، هرچند هم قدرتمند، کارآمد نیست. این مسایل سرآغازی بر cloud computing و مفهوم web scale بودن نرم افزار را با خود همراه داشت. قاعدتا اگر نرم افزاری web scale باشد، احتمال فراوان توانایی پاسخگویی به صدها هزار وظیفه به صورت همزمان را می بایست داشته باشد و این نیاز، یکی از اصلی ترین نیازهای روز نرم افزار در قرن ۲۱ است. آنچه که Amdahl&#x27;s Law سعی در مدل سازی آن داشته است. عمده دلیلی که از Go به عنوان زبان نرم افزارها در قرن ۲۱ یاد می شود بکارگیری همزمانی (concurrency) در توسعه و پیاده سازی نرم افزارهای web scale است (یکی از ۱۲ فاکتور پیشنهادی برای cloud native application که توسط Heroku مطرح شده است و به عنوان best practice سعی در پیروی از آن شده است).قاعدتا استفاده از همزمانی نیازمند پارادایم فکری متفاوتی در برنامه نویسی است که شاید در زبان های برنامه نویسی که تاکنون تجربه کرده اید، وجود نداشته است. این مورد در زبان Go نیز صادق است. در کنار استفاده از ابزارهای توسعه همروند در زبان Go، نیازمند استفاده از الگوهایی بهینه و کارآمد در پیاده سازی همروند هستیم که در مواجه شدن با مسایل با الگوی فکری یکسان از آن استفاده نماییم. آنچه که از آن به عنوان concurrency Pattern یاد می شود. حال چرا باید از آنها استفاده کنیم؟ بدون آنها امکان توسعه web scale یا cloud native application وجود ندارد؟ پاسخ ساده است. چرا وجود دارد. ولی توجه به این نکته مهم است که تفکر مبتنی بر concurrency سخت و پیچیده است. در حل مسایل به شیوه همروند همواره عینک بدبینی به بروز مسایلی مانند: race condition, memory access synchronization, atomicity, deadlock, livelock, starvation بر چشم وجود دارد. با استفاده صحیح از الگوهای همروندی سعی در جلوگیری از بروز این دسته از موارد داریم. از طرفی استفاده از الگوهای همروندی در اصطلاح idiomatic است و طیف گسترده ای از مهندسین نرم افزار در Go آن را درک می کنند. لذا استفاده از این الگوها بسیار پیشنهاد می شود.به عنوان مثال به وسیله الگوهای همروندی سوالاتی از این دست را پاسخ می دهیم:- چگونه یک گو روتین را cancel کنیم یا حداکثر زمانی برای انجاک آن در نظر بگیریم (cancellation pattern و context pattern)؟ - چگونه دیتای گسسته را برای انجام یک وظیفه در چندین گام آماده کنیم (generator and pipeline pattern)؟ - چگونه در pipeline امکان بهینگی در یکی از گام ها را خواهیم داشت(fan out and fan in pattern)؟ - چگونه به روشی مناسب نتیجه n (تعداد n نامشخص) channel که به صورت موازی یا همروند در حال اجرا برای دسترسی به سریعترین جواب هستند را بدست آوریم (or channel)؟ - هنگامی که میخواهیم یک فقره داده به چندین عملیات فرستاده شود، به عنوان نمونه هم دیتا پردازش شده و هم لاگ از آن تهیه شود از چه الگویی برای آن استفاده نمود (tee pattern)؟ - زمانی که ترتیب برای ارسال و دریافت اطلاعات وجود دارد از چه الگوی همروندی میتوان استفاده نمود(bridge pattern همان که در بعضی از کدها از آن به عنوان channel of channels  یاد می شود)؟ - چگونه مدت زمان locking در یک وظیفه را کاهش دهیم(queuing pattern)؟- چگونه عملکردی مانند promise در زبان Go داشته باشیم(future pattern)؟هدف و شیوه بررسیدر مجموعه ای از مقالات در نظر دارم که در برخی از الگوهای همروندی که در پیاده سازی بیشتر کاربرد داشته (حداقل برای خود من) را مورد بررسی قرار دهم و مطالب در مورد هر یک را در حد توان به اشتراک بگذارم. برای  شرح الگو دو مورد رو مد نظر قرار خواهم داد. اول، به بیان حالتی که در آن می توان از الگوی مورد نظر استفاده نمود، خواهم پرداخت (نام این مرحله را situation قرار دادم). سپس الگوی مورد نظر را با جزییات بیشتر شرح داده (نام این مرحله را pattern details قرار دادم) و در نهایت نمونه ای کامل تر از مورد استفاده آن ارایه خواهم داد. در تمام مراحل بررسی الگوها فرض بر این است که با مفهوم done channel آشنایی وجود دارد. برای اشاره ای مختصر به این نوع channel می بایست گفت که از آن به عنوان نمونه ای ساده تری از context برای کنسل نمودن روند اجرای گوروتین استفاده می شود. به این معنی که در صورتی که بخواهیم روند اجرای یک گوروتین را کنسل کنیم از این channel استفاده می کنیم. استفاده از نوع {}struct برای این نوع channelها که صرفا نشان دهنده یک event و نه نوعی خاص هستند، idiomatic است.شرایط محتمل (situation)حالتی را در نظر بگیرید که به صورت متناوب در انتظار دریافت داده ای از یک channel هستید تا وظیفه خاصی را انجام دهید، تا در نهایت در شرایط خاصی آن channel بسته شود، یا توسط done channel یک event جهت توقف اجرای گوروتین صادر شود. در حالتی به نسبت مشابه، شرایطی را در نظر بگیرید که متناوب قصد انجام یک وظیفه را دارید تا زمانی که دستور توقف گوروتین به وسیله done channel صادر شود. برای روشن تر شدن این مورد دو تصویر در زیر نمایش داده شده است:انتظار دریافت یا عملیات پیش فرض یا توقف گوروتینانجام متناوب یک عملیات یا خاتمه اجرای گوروتیندر شرایطی دیگر، مجموعه ای از داده های گسسته داریم که قصد داریم برای اجرای همروند (بدون در نظر گرفتن ترتیب) یک وظیفه بر روی هر یک از داده های آن مجموعه، آنها را برای مقداردهی به channel ها و ورود به pipeline آماده نماییم [به این عملیات به عنوان بخشی از الگوی pipeline، در اصطلاح generator گفته می شود که در زمان بررسی الگوی pipeline در آینده آن را بررسی خواهیم کرد. برای مشخص تر شدن مفهوم pipeline به صورت غیررسمی، بیان این نکته خالی از اهمیت نیست که انجام یک وظیفه در چندین گام ،که در آن، گام ها توسط channel با یکدیگر مرتبط هستند، را pipeline می گویند].منظور از داده گسسته مجموعه داده ای است که مثلا توسط ورودی variadic function ها دریافت می شود، یا به شکل slice ای از داده ها به یک تابع وارد می شود.تبدیل داده گسسته به channelتوسط generatorشرح الگو for-selectدر شرایطی همانند موارد ذکر شده در بالا، از الگویی متداول با عنوان for-select استفاده می شود. این الگو در عین سادگی، بسیار پرکاربرد است. حالت ساده استفاده از این الگو به صورت زیر است:نمونه ساده از for-selectدر این حالت، در یک حلقه بی پایان در انتظار توقف اجرای عملیات گوروتین توسط done channel هستیم و در زمانی که مقداری از doneدریافت نشده باشد، وظیفه مورد نظر (بخش کامنت گذاری شده) اجرا می شود. لازم به یادآوری است که برای اینکه select منجر به block گوروتین جاری نشود از default استفاده می نماییم.نمونه کد فوق، می تواند به صورت مشابه زیر نیز نوشته شود. یعنی بدنه وظیفه به درون default انتقال یابد:نمونه ساده از for-selectدر حالتی دیگر، که خواهان این هستیم که در صورت وجود مقدار در یک channel یک عملیات، و در صورت نبود آن، عملیات دیگری اجرا شود، می توانیم این الگو را به صورت زیر بازنویسی نماییم [این شرایط همانند if-else در حلقه بی نهایت و به صورت همروند حاصل از مقادیر در channel شبیه سازی می شود]:نمونه دیگری از الگوی for-selectدر شرایطی که مجموعه ای از داده گسسته داشته باشیم شرایط کمی متفاوت خواهد بود. در این حالت قصد داریم تا داده را برای ورود به pipeline پردازش آماده نماییم (صرف نظر از نوع پردازش batch یا discrete)، لذا  به جای حلقه بی نهایت، از range بر روی داده های ورودی استفاده می کنیم:الگوی for-selectبرای تبدیل داده های گسسته به ورودی به channelدر نمونه فوق در صورتی که doneمقدار داشته باشد، گوروتین خاتمه می یابد. و در صورتی که stream در انتظار دریافت داده باشد گوروتین در select می بایست block شود و case در آن خط از برنامه در انتظار دریافت داده از slice خواهد بود.شرایط در دنیای واقعیالگوی for-selectهمانطور که در بالا در مورد آن بحث شد، در تئوری پیچیدگی خاصی ندارد ولی آیا در عمل اینگونه است؟ پاسخ: خیر. نکته مهم در مورد نمونه کدهای بالا در این است که اجرای repetitive task در حلقه های بی نهایت، میزان clock بالایی از CPU را به خود تخصیص خواهد داد. این موضوع منجر به این خواهد شد که به احتمال بسیار زیاد توان اجرای گوروتین دیگری را نداشته باشد. این موضوع در runtime به وسیله work stealing حل شده است ولی هدف، اجرای single task نیست.راه حل پیشنهادی چیست؟ در ساده ترین راه حل، جهت جلوگیری از این مشکل، استفاده از مکانیزم های interval یا sleep پیش از اجرای تکه کد non-preemptive رایج است. همانند آنچه که در زیر نمایش داده شده است:راه حل جایگزین برای مقابله با cpu clock بیشمارراه حل جایگزین برای مقابله با cpu clock بیشمارمشکل دیگری که در عمل ممکن است رخ دهد، این نکته است که select در زمانی که امکان بیش از یک case را داشته باشد، به صورت تصادفی یکی از آنها را انتخاب و اجرا می کند. به عنوان نمونه هم done و هم case دیگری امکان اجرا داشته اند. این عمل می تواند بسیار تاثیرگذار باشد. مثلا توقع ذخیره سازی n رکورد را داشته اید ولی n+1 رکورد ذخیره شده است. در این زمان راه حل چیست؟ در مواقع این چنین راه حل ساده جدا سازی case ها با قابلیت همزمانی non-blocking بالا از یکدیگر و قرار دادن آن در دو select مجزا است. به عنوان نمونه به نمونه کد زیر توجه کنیدجداسازی و کاهش احتمالدر نمونه بالا فرض بر این بوده که احتمال دریافت داده از stream و انجام عملیات non-preemptive بسیار بالا بوده است. لذا با اولویت در دریافت از stream و جدا سازی انها احتمال آن را بسیار کاهش می دهیم (همانند اینکه critical section را محدود تر کرده باشیم).معمولا این جدا سازی ها در شرایطی مورد کاربرد دارد که case خاتمه دهنده و case حالت نرمال در اجرا احتمال امکان اجرای بالایی داشته باشند.خاتمهاز وقتی که برای مطالعه این مقاله قرار دادید متشکرم. ممنون میشم تا نظراتتون در مورد این مقاله رو با من به اشتراک بگذارید و در بهتر شدنش من رو کمک کنید. از اصلاحات پیشنهادی و راه حل های جایگزین استقبال خواهم کرد.</description>
                <category>Pleasure of Golang Programming</category>
                <author>علی فرهمند</author>
                <pubDate>Sat, 18 Jul 2020 22:54:09 +0430</pubDate>
            </item>
                    <item>
                <title>پیشتاز مانند Delve در خطایابی برنامه به زبان Go</title>
                <link>https://virgool.io/pogopr/%D9%BE%DB%8C%D8%B4%D8%AA%D8%A7%D8%B2-%D9%85%D8%A7%D9%86%D9%86%D8%AF-delve-%D8%AF%D8%B1-%D8%AE%D8%B7%D8%A7%DB%8C%D8%A7%D8%A8%DB%8C-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%D8%A8%D9%87-%D8%B2%D8%A8%D8%A7%D9%86-go-l2o5qwkzp6a3</link>
                <description>بخشی از یادگیری هر زبان برنامه نویسی، آشنایی و کاربرد ابزارهایی یه که استفاده از اون زبان رو تسهیل میکنه. یکی از مهمترین ابزارها Debugger ها هستن که در زمان بروز باگ در برنامه (چه در زمان توسعه چه در محیط production) یا در زمان فهم چگونگی عملکرد قابلیت های زبان (یادگیری و آموزش) تا حد بسیار زیادی عامل هایی کمک کننده هستن. در واقع بهترین نکته در انتخاب یک debugger برای یک زبان برنامه نویسی یک عبارت ساده است:Must But Not Least Provide Fully Featured Language Support Easily.در مورد زبان برنامه نویسی Go به دلیل قابلیت امکان اجرای concurrent توسط goroutine ها، این مورد کمی متفاوت شاید کمی هم پیچیده تر باشه و قاعدتا استفاده از ابزارهای دیباگ general تمامی قابلیت های زبان برنامه نویسی Go رو تحت پوشش قرار نده. در مورد Go انتخاب از میان debugger ها محدود هستن و به چند انتخاب بیشتر ختم نمیشن که مهترین اونها Go-debug و Delve هستن. نکته مهم و شاید ضعف در مورد Go-debug این هستش که باید در کد تغییر ایجاد کرد تا امکان دیباگ فراهم بشه (برای دیباگ باید کد نوشت!) که این مورد، امکان دیباگ ساده (Easily) رو از کاربر میگیره (البته اگر این نکته رو در نظر نگیریم که این پروژه deprecate شده و این خودش یه ضعف بزرگ محسوب میشه).در این مقاله قصد دارم تا به شیوه استفاده از Delve به عنوان ابزار پیشتاز در debugging کدهای زبان Go و روش کاربرد اون از طریق محیط خط فرمان (terminal، shell و امثال اونها) بپردازم. Delve به عنوان مهمترین ابزار دیباگ برای زبان Go شناخته میشه که تمامی قابلیت های این زبان از جمله goroutine ها رو تحت پوشش قرار میده. دو نکته مهم در مورد این ابزار باید ازش یاد کنیم. قابلیتی به عنوان multiple start scenario، به این مفهوم هست که این قابلیت وجود داره که هم در زمان توسعه و هم در هنگامی که برنامه به صورت فایل اجرایی سیستم عامل در حال اجراست امکان دیباگ فراهمه. نکته دوم اینکه IDE های موجود تا حدی delve رو پشیتبانی میکنن [شاید دلیل اینکه ترجیح میدم در از delve توی خط فرمان استفاده کنم همین partially support شدنش در IDE هاست. چون قابلیت و انعطاف بیشتری رو فراهم میکنه].شروع کار با Delveبرای شروع کار باید delve در سیستم عامل نصب شده باشه. نصب delve کار پیچیده ای نیست. برای این کار میتونین از راهنمای موجود در github این دیباگر در لینک زیر استفاده کنین. https://github.com/go-delve/delve/tree/master/Documentation/installation نصب بسیار راحته (البته به جز macOS که پیش نیازهایی رو داره) که با استفاده از دستور get به عنوان یکی از ابزارهای دانلود package ها و dependency ها در Go این عمل به راحتی انجام میشه.go  get  github.com/go-delve/delve/cmd/dlvبعد از اجرای این دستور و پس از خاتمه اون، delve بر روی سیستم عامل شما نصب شده و آماده استفاده است. برای اطمینان از اینکه در سیستم عامل نصب شده دستور زیر رو میتونین توی terminal اجرا کنین.dlv  versionدرصورتی که به درستی همه چی پیشرفته باشه خروجی مثل تصویر زیر رو دریافت می کنین:اطمینان از نصب delveنکته مهمی که باید در نظر گرفت این هست که اگر از Visual Studio Code استفاده می کنین و از پلاگین Go استفاده میکنین delve همراه با نصب این پلاگین نصب میشه و احتیاجی به نصب مستقیم اون نیست. فقط جهت اطمینان ورژن delve رو با دستور بالا یکبار کنترل کنین تا از نصب اون اطمینان پیدا کنین. مدل اجرایی delve به صورت halt and catch هستش. به این مفهوم در ابتدا باید پراسسش رو اجرا کنیم تا شروع به listen کردن به دستورات روی پورت خاصی بکنه. برای نمایش اولیه شیوه کار با delve در gif زیر نحوه تعاملش رو براتون نمایش دادم.شروع کار با delveبرای شروع، برای اینکه کارکردش کمی واضحتر بشه دستور زیر رو توی مسیر پروژه تون اجرا کنین:dlv  debug  &lt;path to the main file in your project&gt;با زدن این دستور Delve آماده catch کردن دستورات شما میشه. در مورد دستورات جلوتر به طور کاملتر بحث خواهم کرد ولی الان اگر دستور رو اجرا کرده باشین می بینین که delve آماده شنیدین دستورات شما میشه. میتونین با زدن دستور help لیست دستوراتش رو ببینین (در تصویر مشخص شده). برای قرار دادن breakpoint در خطی از برنامه که در مسیر دستور debug مشخص کردین میتونین از ترکیب اسم فایل و خط مورد نظرتون استفاده کنین. مثلا برای اینکه در فایل main.go در خط پنجم breakpoint قرار بدین به راحتی میتونین از دستور زیر استفاده کنین:(dlv) break main.go:5 با زدن این دستور اطلاعاتی از قرار دادن breakpoint در خط مورد نظرتون نمایش داده میشه. بعد میتونین با دستور continue شروع به اجرای برنامه برای دیباگ کنین. با این کار دیباگ آغاز میشه و در خط پنجم در خروجی قرار میگیره (توی تصویر بالا نمایش داده شده). نکته زیبای این قسمت اینه که همیشه چند خط بالا و پایین تر از breakpoint رو به شما در خروجی نشون میده که دید کلی از تکه کد در حال دیباگ داشته باشین. میتونین با دستور step گام به گام جلو برین و مثلا با دستور locals مقادیر متغییر هاتون رو در هر لحظه مشاهده کنین (در تصویر بالا در هر مرحله locals رو اجرا کردم و مقادیر دو متغییر رو در هر لحظه نمایش دادم. نکته ای که باید مد نظر قرار بگیره وجود مقدار garbage قبل از مقداردهی اولیه هر متغییره که تا قبل از قرار گرفتن مقدار مشخص شده، مقدار garbageدر خودش داره. نکته دیگه هم اینکه تا زمان رسیدن به دستور در y:= 20 این متغییر در حافظه قرار نگرفته). در آخر هم میتونین بعد از انجام دیباگ با دستور quit از delve خارج بشین (همیشه برای خروج از quit استفاده کنین. عملکردی مثل defer رو در اختیارتون قرار میده). این مقدمه ای از شیوه تعامل با delve بود که برای اینکه بتونیم بیشتر داخل جزییاتش بشیم. روالی که بیان کردم روالیه که به صورت متناوب شما در هربار استفاده از delve تکرار و تکرار میکنین. دسته بندی دستورات Delveدستورات delve به شش دسته اساسی تقسیم میشن:دسته بندی دستورات delveهر دستوری که در delve بکار گرفته بشه به یکی از این شش دسته تعلق داره. کار رو با دسته ساده تر یعنی دسته دستورات اجرای عملیات دیباگ شروع کنیم.دسته اول: دستورات اجرای دیباگ:این دسته ساده ترین دستورات برای اجرای عملیات دیباگ رو برعهده دارن و مهمترین دستورات اون اینها هستن:دستورات اجرای دیباگدستورات اجرایی دستورات ساده ای هستن که توی روال روزمره برای دیباگ کد در Go ازشون استفاده می کنیم. با مفاهیم این دستورها اگر با زبان برنامه نویسی دیگه ای کار کرده باشین (که فرض من بر اینه) و از ابزارهایی برای اون زبان استفاده کرده باشین حتما آشنا هستین. دستور continue  عملکردی شبیه trigger داره که با شروع فرآیند دیباگ عموما اولین دستور بعد از تعیین خط های breakpoint هستش. برای نمایش این دستورات و شیوه عملیاتیشیون یه نمونه برنامه ساده از اینترنت پیدا کردم و برای اجرا و استفاده از ترکیب این دستورها در زیر نمایش دادم.استفاده از دستورات اجرای دیباگهمانطور که مشخصه از دستور break برای قرار دادن چند breakpoint توی کد استفاده کردم و با استفاده از دستور continue، step و stepout کد رو پیمایش کردم. نکته ای که قابل اهمیته اینه که برای راحتی در اجرای دستورات و تایپ اونها از abbreviation دستورات هم میشه استفاده کرد. مثلا برای اجرای دستور continue میشه به راحتی از c استفاده کرد. یا برای step از s و برای stepout از so.دسته دوم : Breakpoint هااین دسته از دستورات برای مدیریت breakpoint ها مورد استفاده قرار می گیرن.دستورات مرتبط با breakpoint هاتا حدودی با مفاهیم این دستورات آشنا هستین. مثلا برای قرار دادن یک breakpoint در کد تا حالا چندین بار از دستورbreak (یا مختصر اون b) در دیباگ استفاده کردیم. برای اینکه لیت تمامی breakpoint ها رو نمایش بدیم میتونیم از دستور bp یا همون breakpoints استفاده کنیم. دو دستور بعدی برای حذف breakpoint از یک خط خاص از برنامه مورد استفاده قرار می گیرن. با این تفاوت که برای حذف یک breakpoint به وسیله دستور clear باید شناسه اون breakpoint رو که در زمان قراردادنش در خروجی نمایش داده شده بوده رو به عنوان پارامتر ورودی به این دستور بدیم. دستور condition برای توقف در اجرای یک breakpoint در شرایطی خاص هست. همونطور که حدث زدید باید breakpoint قبلش مشخص شده باشه تا بشه از این دستور استفاده کرد. فرمت دستور اینطوریه condition &lt;breakpoint number&gt; &lt;boolean condition&gt;مثلا برای اینکه در خط نهم از تصویر سورس کد بالا بخوایم در صورتی که i==3 بود breakpoint اجرا بشه از دستور زیر استفاده میکنیم. باز هم تاکید میکنم که باید قبلش با دستور break توی خط ۹ breakpoint ایجاد کرده باشین. (ممکنه شماره breakpoint شما این شماره ۱ نباشه. برای این مورد به خروجی دستور break زمان اجراتون دقت کنین تا شماره breakpointدرست رو استفاده کنین)(dlv) condition 1 i==3دستور trace مانند دستور break هستش با این تفاوت که این دستور اجرا برنامه در زمان دیباگ رو halt نمی کنه و تنها در صورتی که به tracepoint مورد نظر رسید میتونه hint خاصی بده. به عنوان نمونه اگر بخوایم در خط ۹ از نمونه کد بالا یه  tracepoint ست کنیم و به اون اسم خاصی بدیم که در زمان عبور کد از اون نمایش داده بشه میتونیم از دستور زیر استفاده کنیم:(dlv) trace sampleHint main.go:9 با زدن این دستور و رسیدن اجرای دیباگر به اون در خروجی hint ایی با عنوان sampleHint در خروجی نمایش داده میشه. دستور on برای زمانی هست که میخوایم با رسیدن به اجرای یک breakpoint خاص دستور خاصی اجرا بشه. مثلا پیامی در خروجی چاپ بشه. فرمت دستور به این صورته(dlv) on &lt;breakpoint number&gt; &lt;command&gt;
(dlv) on 1 print &amp;quotThis is a sample event&amp;quotبرای مشخص شدن بیشتر نمونه کد اجرایی رو برای این مجموعه از دستورات به صورت نمونه دیباگ کردم و توی تصویر نتیجه اون رو قرار دادم:دستورات breakpointدسته سوم: دستورات نمایش متغییرها و حافظهاین دسته از دستورات در ارتباط با نمایش وضعیت حافظه و مقادیر متغییرها در زمان توقف دیباگ هستن که در تصویر زیر خلاصه ای از مهمترین های اونها رو اوردم:دستورات مرتبط با حافظه و متغییرهادستورات این دسته دستورات ساده ای هستن. فقط ذکر چند نکته میتونه مهم باشه. دستور display باید یک عبارت مثل نام یک متغییر رو به عنوان ورودی بگیره تا در هر زمان اجرا halt بشه اون مقدار رو نشون در اون لحظه محاسبه و نشون بده. فرمت استفاده از این دستور هم در زیر بیان کردم:(dlv) display -a &lt;expression&gt;
(dlv) display -a tempمثلا دستور بالا با قطع اجرا در هر گام مقدار متغییر temp رو نشون میده. نکته دیگه در مورد دستور vars هست که مقادیر متغییر ها در سطح package رو نشون میدن. این دستور میتونه بایک regex استفاده بشه تا متغییر های در سطح package با اون الگو رو نشون بده. دستور set هم باید به محدودیتش توجه بشه که تنها اعداد و اشاره گرها قابل تغییر هستن. برای واضح تر شدن این دسته از دستورات تصویر زیر رو قرار دادم:تعامل با حافظه و متغییرهادسته چهارم: goroutine و threadمهمترین تفاوتی که میتونیم بین delve و سایر دیباگرها بیان کنیم، همین فهم و درک goroutine های زبان Go هست. این دسته چند دستور بیشتر نداره ولی بسیار کارآمد.دستورات مرتبط با thread و goroutineعملکرد این دستورها مشخصه. برای اینکه متوجه بشیم goroutine جاری کدوم goroutine هست میتونیم از دستور goroutine (یا خلاصه اون gr) استفاده کنیم. در صورتی که خواستیم از بین یک goroutine به یه goroutine دیگه سوییچ کنیم و اون رو در حال اجرا قرار بدیم میتونیم از همین دستور با پارامتر شماره goroutine ایی که قصد سوییچ کردن بهش رو داریم، استفاده کنیم.(dlv) goroutine &lt;number of goroutine to switch&gt;یه کاربرد دیگه ای که میتونیم از دستور goroutine داشته باشیم این هستش که میتونیم همزمان با سوییچ کردن بین goroutine ها دستور خاصی رو هم اجرا کنیم.(dlv) goroutine 20 set x=12این دستور به goroutine شماره ۲۰ سوییچ میکنه و در صورتی که متغییر x در اسکوپش وجود داشته باشه مقدار اون رو به ۱۲ تغییر میده.همین موارد در مورد دو دستور بعدی، البته با محدودیت به مراتب بیشتر، هم صدق میکنه با این تفاوت که دستور thread فقط برای switch بین thread ها با شماره thread مورد نظر مورد استفاده قرار میگیره. برای بهتر مشخص شدن کارایی این دستورها تصویر زیر رو قرار دادم:استفاده از دستورات goroutineدسته پنجم: دستورات stack و frameدستورات مرتبط با stack و frameاین دسته از دستورات دارای دو دستور پراهمیت تر هستند که در طول دیباگ برنامه ها بیشتر مورد استفاده قرار می گیرن. این دو دستور deferred و دستور stack هستن. دستور stack که همونطور که از اسمش هم پیداست برای نمایش stack در زمان توقف اجرای برنامه در دیباگ مورد استفاده قرار می گیره. دستور deferred هم برای اینکه در n امین دستور defer در تابع عبارتی رو نمایش بده بکار میره. مثلا در صورتی که کدی داشته باشیم که دریک تابع اون چندین دستور deferاستفاده شده باشه، برای اینکه در defer شماره n عبارتی نمایش داده بشه میتونیم از دستور زیر استفاده کنیم:(dlv) deferred 2 print xنکته ای که باید در مورد عبارت قابل نمایش در deferred مد نظر داشته باشیم این هستش که دستورات محدود هستن و به سه دستور locals، args و print ختم میشن.در مورد دستور stack هم گفتن این نکته میتونه کمک کننده باشه که stack -full نمایش کاملی از فریم استک رو به شما نشون خواهد داد. این دسته دستورات دیگه ای مثل frame و up و down هم داره که دستورات سطح پایین تری توی دیباگ هستن و ازشون کمتر استفاده میشه. به همین دلیل ترجیح دادم تا در آپدیت های بعدی این مقاله در موردشون بنویسم.دسته ششم: سایر دستوراتاین دسته از دستورات اگرچه کم اهمیت تر جلوه داده شدن، ولی قدرت بالایی رو در اختیار توسعه دهنده قرار میدن. این دستورات رو با هم مرور میکنیم:دستور sourceدستور source از کاربردی ترین دستورات delve به شمار میاد. برای اینکه عملیات دیباگ به صورت مشخص و با اجرای دیباگ آغاز بشه و از دستورات تکراری در دیباگ یک کد به صورت مداوم جلوگیری بشه، میتونیم از این دستور استفاده کنیم. به این صورت که تمامی دستوراتی رو که قراره در حین اجرای دیباگ قصد ورودش در console رو داریم رو در یک فایل قرار میدیم و با شروع اجرای دیباگ و زدن این دستور به همراه آدرس اون فایل، دستورات درون فایل به صورت متناوب شروع به اجرا میکنه.استفاده از دستور sourceدر این دسته از دستورات مجموعه دستوراتی با اولویت پایین تر هم وجود دارن که فقط به صورت خلاصه کاربردشون رو ذکر میکنم.سایر دستوراتاستفاده از Flag هاو سایر دستوراتتاحالا تمامی عملیاتی که در delve در موردشون صحبت کردیم، از دستور debug استفاده میکردیم. توی این بخش قصدم اینه یکم در مورد بقیه command ها هم صحبت کنم. در کنار دستورات اجرایی در هنگام دیباگ در delve، خود دستور دارای یه سری فلگ کاربردی هست که در بسیاری از مواقع به شما تو کاربرد delve کمک میکنه. در این بخش قصدم اینه که به مهمترین فلگ ها اشاره کنم و در مورد کاربردهاشون مثلاهایی واقعی تری بیان کنم. اگر خود دستور dlv رو بدون هیچ پارامتری در محیط terminal تون وارد کنید خروجی مثل تصویر زیر می بینین که دو بخش دستورات و فلگ ها رو منحصرا مدنظر قرار دادم:دستورات و فلگ هاحالا در مورد مهمترین دستورات و فلگ ها صحبت کنیم:دستور connect و فلگ های headless و listenدر مواقعی قصد دارین که در یه محیط indirect مثلا sandbox یا در یه container کد رو دیباگ کنین. مثلا برنامه در یک docker container در قالب یه pod در Kubernetes در حال اجراست و شما قصد دارین متناسب با دیتای production یا staging، و در همون لحظه امکان دیباگ کد رو داشته باشین. در این حالت باید این امکان فراهم شده باشه که شما یه debug server با یک port مشخص در container تون ایجاد و port اش رو expose کرده باشین. این کار توسط دستور debugو به وسیله فلگ های headless و listen قابل انجامه به اینصورت که شما در Dockerfile میتونین در هر زمان که نیاز داشتین از این عبارت برای اجرای کد همزمان در debug mode استفاده کنین:dlv debug --headless --listen=:6666 main.goاینکار باعث میشه که برنامه در مد دیباگ یه debug server به وجود بیاره که به پورت ۶۶۶۶  در حال listen کردنه (روش های دیگه ای وجود داره که مناسب تر هستن. اینجا فقط برای توضیح مکانیزم، این روش رو استفاده کردم. در مورد پورت ۶۶۶۶ اگر در k8s باشیم سرویسی که این پورت رو راوت میکنه شرایطی باید داشته باشه که از اون هم صرف نظر کردم. اونجا معمول برای این دست کارها اینه که محدوده پورت ها بالای ۳۰۰۰۰ باشن). بعد برای اتصال به سرویس دیباگ روی اون پورت از دستور connect میشه استفاده کرد.dlv connect 127.0.0.1:6666که IP قابل کانفیگه. برای نمونه تصویر زیر رو برای شبیه سازی headless ببینین:استفاده از headless serviceمابقی فلگ ها کاربری ساده ای دارن که با نگاه کردن به خروجی دستور dlv میشه به راحتی کشفشون کرد. مثلا کاربرد log که اطلاعات بیشتری رو حین دیباگ در اختیار قرار میده یا init که کاربردی شبیه دستور source داره رو میشه از مستنداتش به راحتی به دست آورد. که این مورد رو به خودتون واگذار میکنم.سایر دستورات dlvعلاوه بر debug که دستور پرکاربرد برای delve هستش دو دستور مهم دیگه هم وجود داره که در برخی زمان ها کمک کننده هستن. اولین دستور dlv exec هستش که کمک میکنه تا کد کامپایل شده رو بتونیم دیباگ کنیم. نکته مهم اینه که کامپایلر زبان Go بعد کامپایل کد optimized شده تولیر میکنه که این مورد استفاده از این دستور رو خیلی سخت و غیرقابل فهم میکنه. برای اینکه بشه از این مشکل عبور کرد باید در زمان کامپایل کدها فلگ زیر رو استفادع کنین تا قابلیت کامپایل کد اجرایی رو داشته باشین:go build -gcflags=&amp;quot-N -l&amp;quot &lt;path to the main file in main package&gt;بعد از این شیوه استفاده در کامپایل و build کد به راحتی میتونین بادستور زیر فایل اجرایی رو دیباگ کنین.dlv exec &lt;path to executable file&gt;دستور بعدی که خیلی شبیه exec کار میکنه دستور attach هستش که در صورتی که مثل execبا gcflags که بالا توضیح دادم رو استفاده کرده باشین میتونین با PID به فایل اجرایی کد Go متصل بشین و دیباگ انجام بدین.dlv attach PIDخاتمهخیلی دوست داشتم مطلبی در مورد gdlv هم بنویسم ولی احساس میکنم ممکنه از حوصله خارج بشه. به همین دلیل این ابزار visual برای delve رو فقط معرفی میکنم که بتونین ازش استفاده کنین. کار باهاش بسیار ساده است (البته من خودم استفاده نمیکنم :)). اگر مفاهیمی که توضیح دادم رو آشنا شده باشین، به راحتی میتونین ازش استفاده کنین. https://github.com/aarzilli/gdlv خوب فکر کنم تونسته باشم یکم از توانایی های delve رو به دوستان علاقه مند نشون داده باشم. ممنون میشم که نظراتتون و اصلاحاتتون رو برام ایمیل کنین یا زیر همین مقاله بهم اطلاع بدین. پیشاپیش از شما متشکر خواهم بود :)</description>
                <category>Pleasure of Golang Programming</category>
                <author>علی فرهمند</author>
                <pubDate>Sat, 13 Jun 2020 20:34:38 +0430</pubDate>
            </item>
                    <item>
                <title>آناتومی channelها در زبان برنامه نویسی Go</title>
                <link>https://virgool.io/pogopr/%D8%A2%D9%86%D8%A7%D8%AA%D9%88%D9%85%DB%8C-channel%D9%87%D8%A7-%D8%AF%D8%B1-%D8%B2%D8%A8%D8%A7%D9%86-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%D9%86%D9%88%DB%8C%D8%B3%DB%8C-go-cykdapfq8liy</link>
                <description>هر زمان از توسعه دهنده ها در مورد علت انتخاب زبان برنامه نویسی Go برای یادگیری یا توسعه در پروژه ها سوال می شه پاسخ هایی بعضا ساده، گاه بلند در توصیف نقاط قوت این زبان می شنویم. در تمامی این پاسخ ها همیشه دو مورد مشترک وجود داره که همه توسعه دهنده ها به اون اشاره می کنن: Concurrency و Performance. شاید ساده ترین و در عین حال کامل ترین پاسخ به این سوال یک جمله ساده باشه:چون زبان برنامه نویسی Go سریعه.فاکتورهای متنوعی  رو میشه به عنوان علل سریع بودن زبان برنامه نویسی Go مطرح کرد که مهمترین و جذاب ترین اون قابلیت ذاتی concurrency در این زبان هستش. این قابلیت توسط سه تفنگدار concurrency در این زبان برآورده میشه. در واقع مثلث goroutine، channel و sync package نقش اصلی و در واقع ابزار دستیابی به concurrency  در این زبانه. سه رکن اساسی concurrency در زبان Goقطعا شما، حتی اگه آشنایی کمی با این زبان داشته باشین، بر مبنای مدل پیاده سازی شده از CSP برای پشتیبانی از concurrency در این زبان، حتما این جمله رو در مورد فلسفه زبان Go شنیدید:Do not communicate by sharing memory; Share memory by communicating.همونطور که میدونید، برای رسیدن به این هدف channel ها پیشنهاد اول (نه لزوما بهترین) عموم توسعه دهنده های با تجربه در زبان Go هستش. در واقع channel ها دو وظیفه ساده رو برعهده دارن: برقراری ارتباط مابین goroutine ها و همچنین synchronization در دسترسی به حافظه مشترک. در این مقاله قصد آموزش و پرداختن به شیوه استفاده از channel ها رو ندارم و فرضم بر این اساسه که شما درک هرچند ساده و کم از channelها دارین، چون معتقدم هم خارج از حوصله ی خواننده ی این مقاله هستش و هم به تنهایی با یادگیری شیوه استفاده از channelها، هنر بکارگیریه concurrency به دست نمیاد. در این مقاله رویکرد متفاوتی رو دنبال می کنم و قصدم اینه که نگاه سطح پایین تری به شیوه پیاده سازی channel ها در این زبان داشته باشم و بررسی سطحی از چگونگی برخورد go runtime با channel ها داشته باشم.یک مثال سادهpackage main
import (
   &amp;quotfmt&amp;quot
   &amp;quottime&amp;quot
)
func main() {
	ch := make(chan interface{}, 5)
	go func() {
		defer close(ch)
		for i := 0; i &lt; 10; i++ {
			ch &lt;- i
		}
	}()
	for v := range ch {
                time.Sleep(3 * time.Second)
		fmt.Println(v)
	}
}با مثالی ساده کار رو شروع کنیم: همونطور که مشخصه یه شبیه سازی ساده ای از یه  buffered channel با سایز ۵ در نمونه کد وجود داره که در closure مرتبط با goroutine، در یک حلقه با مرتبه ۱۰، ایندکس حلقه به channel انتقال داده میشه و به صورت concurrent با range بر روی متغییر ch مقادیر در صورت در دسترس بودن و close نبودن channel در خروجی چاپ میشه. برای اینکه پردازش مقادیر متغییر v رو زمان بر جلوه بدیم۳ ثانیه در نمایش v تاخیر ایجاد کردیم. عموما به تابعی که وظیفه ارسال داده به channel رو داره sender و به تابعی یا تکه کدی که وظیفه دریافت اطلاعات از channel رو داره receiver گفته میشه. من هم برای توضیح راحت تر از این دو اصطلاح استفاده خواهم کرد [در مورد اینکه چرا تنها یک تابع وظیفه ارسال یا دریافت رو باید بر عهده داشته باشه پیشنهاد می کنم اصل Confinement در زبان Go رو مطالعه کنین]. حالا با هم بررسی کنیم که چطور channel ها باعث میشن که یک ساختمان داده ی FIFO مابین goroutine ها اطلاعات رو به صورت safe منتقل کنه و با چه مکانیزمی با کمک go runtime می تونه goroutine ها را block و unblock می کنه.خوب اول باید به این نکته اشاره کنیم که در واقع وقتی شما یه channel رو با دستور make میسازین در واقع یه نمونه از یه struct به اسم hchan در heap ساخته می شه و اشاره گری به اون، برگردونده میشه (در واقع علت اینکه نوع داده ای chan رفتاری مثل اشاره گر ها داره و یکی از استثناها در قانون always pass by value در زبان Go هست همین مورده که در واقع یک اشاره گری به نقطه ای از heap هستش). این struct چند فیلد مهم داره که توی بررسی دقیقتر رفتار channel کمک کننده است:ساختار ساده سازی شده ی hchanفیلد buffer اشاره گری به یه صف حلقوی (recursive queue) هست که ابتدا و انتهای اون توسط sendx و recvx مشخص میشه.  هر زمان که مقدار جدیدی قراره به buffered channel اضافه بشه در حقیقت چند استپ ساده اتفاق میوفته:اول فیلد  lock که یه mutex ه، برای ورود به critical section با لاک کردن یه دسترسی انحصاری بوجود میاره. دوم مقدار مورد نظر در صورتی که فضای کافی هنوز ساختمان داده ی buffer وجود داشته باشه کپی میشه. سوم مقادیر sendx برای نمایش ایندکس send جدید بروز رسانی میشه و در نهایت مقدار qcount که در لحظه تعداد عناصر در بافر رو داره بروز رسانی میشه. با اتمام این کارها، lock در نهایت release میشه [برای اینکه ببینید mutual exclusion چطوری عمل میکنه پیشنهاد میکنم به sync در کتابخونه استاندارد Go نگاه بیاندازین. نکته دیگه اینکه در صورتی که با ساختمان داده recursive queue آشنا نیستین یه نکته مهمی رو فقط مد نظر داشته باشین که پیاده سازی اون در Go بر مبنای slice هست که در صورتی که ایندکس sendx و recvx با هم برابر باشن به این معنی هستش که صف پر شده یا اصلا المانی درون اون وجود نداره]. همین عملیات عینا به صورت معکوس برای دریافت یک مقدار از channel هم اتفاق میوفته. یعنی ابتدا lock، سپس کپی مقدار به نتیجه channel و در نهایت بروزرسانی recvx و رها سازی lock [تابع مورد نظر در sync.Mutex به ترتیب Lock و Unlock هستن].نکته مهمی که تا حالا با این بررسی ساده متوجه میشیم اینه که از اونجایی که عملیات enqueue و dequeue از buffer با کپی کردن اطلاعات انجام میشه، در نتیجه channelها با این روش memory safety  رو بوجود میارن. در حقیقت پایبندی به فلسفه زبان رو به این مکانیزم یعنی بکارگیری mutex و کپی مقدار به جاری ارسال pointer به اون داده فراهم می کنن.حال بیاین شرایط رو کمی پیچیده تر در نظر بگیریم. فرض کنین که مثل شبیه سازی ایی که در نمونه کد بالا انجام شده، عملیات پردازش در receiver زمان بر باشه. در این حالت چه اتفاقی میوفته؟ خیلی ساده در حالی که مقادیر اول تا ششم در حال انتقال به buffered channel هستش با دریافت اولین مقدار توسط receiver، هنوز پردازش اولین مورد در receiver هنوز به پایان نرسیده، بافر  channel پر شده و در نتیجه عملیات sender بلاک میشه. همونطور که میدونین channel ها عامل در اصطلاح pause / resume شدن goroutine ها هستن. ولی چطور اینکار رو انجام میدن؟روش ساده است. همونطور که می دونین goroutine ها نگارش انتزاعی سطح بالاتری از coroutine ها هستن که در حقیقت با یه نگارش M:N به تعدادی از thread ها در سطح OS در واقع map میشن، تا عملیات context switching در سطح سیستم عامل که عملیات هزینه بریه رو تعدیل کنن. این نگاشت M:N در واقع توسط بخشی از go runtime به نام runtime scheduler انجام میشه. در واقع برای اینکه بتونه این نگاشت رو نگه داره از مکانیزم با سه تا ساختار دیگه استفاده می کنه که توی تصویر نمایش دادم:چگونگی نگاشت M:N در schedulerاولین ساختار همون M یا OS Thread هستش که با توجه به تعداد core ها قابل مدیریت توسط runtime هستش. M عامل اجرایی goroutine ها در Goهستش. هر یک از OS Thread ها یه لیستی از goroutine های آماده به اجرا دارن که در اصطلاح بهشون runQ گفته میشه. هر زمانی که یه thread قصد اجرا داره (در حقیقت خودش تصمیم نمی گیره، این scheduler ه که بهش میگه چکار کنه) در حقیقت باید یکی از goroutine های درون runQ رو dequeue کنه و به ساختار سوم (فکر کنم به رنگ صورتی) انتقال بده و اجرا رو شروع کنه. این ساختار سوم در حقیقت goroutine یه که الان در حال اجرا شدنه. نکته مهمی که باید مجددا تاکید بشه و در اینجا هم مشهوده که context switching در این سطح به صرفه تر از انجامش در سطح سیستم عامله.حالا برسیم به اصل سوالی که مطرح شد. اگه buffered channel پر باشه و sender بخواد المان جدیدی رو توی بافر channel قرار بده چه اتفاقی میوفته؟ بله درسته sender توسط channel بلاک میشه. حالا واقعا چه اتفاقی میوفته؟در واقع channel وقتی میبینه که پر شده یه فراخوانی به runtime scheduler انجام میده تا نشون بده goroutine یی که الان در حال اجراش هست باید pause بشه. این فراخوانی توسط تابعی به نام gopark اتفاق میوفته. وقتی scheduler این فراخوانی رو دریافت میکنه وضعیت goroutine رو به waiting تغییر میده و ارتباطش رو به عنوان goroutine در حال اجرا با OS Thread قطع میکنه. در این زمان OS Thread از runQ اطلاعات goroutine بعدی که برای اجرا آماده هست رو dequeue میکنه و به اجرا ادامه میده (بدون وقفه). این مراحل رو توی ۵ گام در تصویر زیر نشون دادم.مراحل pause کردن یه goroutine در runtime توسط schedulerحالا چه اتفاقی برای goroutine یی که pause شده (بهش بگیم block) می افته؟ اگر به فیلد های hchan باز دقت کنین دو تا اشاره گر به نام sendq و recvq داره که در حقیقت لیستی از ارسال کننده ها و دریافت کننده های در انتظار رو نمایش می دن. وقتی یه goroutine در انتظار ارسال المانی به channel هستش در حقیقت در لیست sendq قرار میگیره و وقتی که در انتظار دریافت المان از channel باشه در recvq. با block شدن یه goroutine یه ساختار دیگه ای به نام sudog (بخونیمش سو دو جی) ساخته میشه و در sendq قرار میگیره. ساختار sudog پیجیده نیست در وقع یه اشاره گر به goroutine و یه {}interface برای ذخیره مقداری که قراره از طریق channel انتقال داده بشه. باز هم تاکید می کنم که memory safety با کپی کردن این مقدار در sudog اتفاق میوفته. توی تصویر پایین مراحلش رو توی چهار گام نمایش دادم.مراحل پردازش goroutineهای در حال انتظارهر زمان بافر مربوط به channel توسط یه receiver خالی بشه ابتدا از sendq در حال انتظار اولین sudog برداشته میشه و با تغییر وضعیت اون goroutine به runnable در buffer قرار داده میشه. وقتی این تغییر اتفاق میوفته channel به runtime scheduler یه event ی رو با فراخوانی تابعی به عنوان goready انجام میده که باعث میشه تا مجددا اون goroutine  به انتهای لیست runQ اضافه بشه.مراحل resuming یک goroutine بلاک شده توسط schedulerبه نظرم با من موافقین که مکانیزمش جالبه.اینکه چطور در حالتی که به عنوان مثال buffer خالیه و یه receiver انتظار دریافت مقدار اطلاعات از channel رو داره پیچیده نیست (یه نکته جالب داره پیشنهاد میکنم در موردش مطالعه کنین). فقط باید توجه داشت که صف انتظار توی این حالت recvq هستش.خوب فکر کنم نباید از حوصله خارج بشه... ممنونم که وقت گذاشتین و این مقاله رو خوندین. مشتاقم نظرتون رو در این مورد بدونم تا بتونم بهتر و بهتر این مطلب رو کامل کنم و در صورت نیاز اصلاحش کنم.</description>
                <category>Pleasure of Golang Programming</category>
                <author>علی فرهمند</author>
                <pubDate>Thu, 28 May 2020 14:25:24 +0430</pubDate>
            </item>
            </channel>
</rss>