در معماری Vertical Slice، به جای اینکه سیستم را به لایههای مختلف (مثل لایه UI, business logic, data access) تقسیم کنیم، هر قابلیت یا ویژگی را به یک واحد مستقل به نام Slice تبدیل میکنیم. هر Slice شامل تمام اجزای مورد نیاز برای ارائه یک قابلیت خاص است. و هر Slice را می توان به عنوان یک برنامه کوچک با عملکرد متمایز دید.
هدف اینه که کد رو براساس ویژگیها و نیازهای خاص دستهبندی کنیم، نه براساس موارد فنی.
تفکیک مسئولیتها: هر قابلیت تو Slice خودش قرار میگیره، که باعث میشه وابستگیها کمتر و کدها خواناتر بشن.
تست راحتتر: چون هر Slice مستقله، تست کردنش راحتتره.
مقیاسپذیری تیم: تیمهای مختلف میتونن به صورت مستقل رو Slices مختلف کار کنن.
کاهش وابستگیها: سیستم تمیزتر و مدیریت کردنش آسونتر میشه.
انعطافپذیری در تغییرات: تغییرات تو یه Slice معمولاً تأثیری رو بقیه سیستم نداره.
پیچیدگی برای سیستمهای کوچیک: این معماری ممکنه برای سیستمهای کوچیک بیش از حد پیچیده باشه.
کد تکراری: بعضی کدها ممکنه بین Slices تکرار بشن.
یادگیری و تنظیم تیمها: ممکنه یه کم زمان ببره تا تیمها به این معماری عادت کنن.
سیستمهای بزرگ و پیچیده: برای سیستمهایی که ویژگیهای متعددی دارن.
تیمهای چندگانه: وقتی تیمهای مختلف رو قابلیتهای مختلف کار میکنن.
سیستمهای مبتنی بر میکروسرویس: این معماری با میکروسرویسها خیلی خوب سازگاره.
سیستمهای با نیاز به توسعه مستمر: برای سیستمهایی که نیاز به انتشار مکرر و سریع ویژگیهای جدید دارن.
#گولنگ #گو #Go #Golang #Vertical_Slice
Please open Telegram to view this post
VIEW IN TELEGRAM
چطور گوروتینهای گولنگ رو مدیریت کنیم؟💢
تا حالا شده تو برنامههاتون بخواید یه کار طولانی رو نصفهنیمه قطع کنید؟ اینجاست که دو تا ابزار قدرتمند گولنگ یعنی Cancel و Done به کمکتون میان!
💠 Cancel:
فرض کنید یه گوروتین دارید که نمیخواید ادامه بده. با Cancel میتونید مستقیم بهش بگید "بسه، دیگه جلوتر نرو!" و منابعش هم آزاد بشه. این کارو با تابع context.WithCancel انجام میدید و هر وقت ()cancel رو صدا بزنید، همه گوروتینهای مربوط به اون کانتکست متوقف میشن.
💠 Done:
حالا یه حالت دیگه: به جای اینکه دستی گوروتینها رو متوقف کنید، بذارید خودشون بفهمن باید کارشون رو تموم کنن. اینجا Done به درد میخوره. Done یه کاناله که وقتی کانتکست تموم شد (مثلاً به خاطر تایمآوت یا لغو شدن)، بسته میشه و گوروتینها سیگنال میگیرن که "وقت رفتنه!".
⭐️ خلاصه صحبت ها
💢 Cancel برای متوقف کردن مستقیمه.
💢 Done برای سیگنال دادن غیرمستقیمه.
💙 Anophel | آنوفل
#گولنگ #گو #go #golang
تا حالا شده تو برنامههاتون بخواید یه کار طولانی رو نصفهنیمه قطع کنید؟ اینجاست که دو تا ابزار قدرتمند گولنگ یعنی Cancel و Done به کمکتون میان!
فرض کنید یه گوروتین دارید که نمیخواید ادامه بده. با Cancel میتونید مستقیم بهش بگید "بسه، دیگه جلوتر نرو!" و منابعش هم آزاد بشه. این کارو با تابع context.WithCancel انجام میدید و هر وقت ()cancel رو صدا بزنید، همه گوروتینهای مربوط به اون کانتکست متوقف میشن.
حالا یه حالت دیگه: به جای اینکه دستی گوروتینها رو متوقف کنید، بذارید خودشون بفهمن باید کارشون رو تموم کنن. اینجا Done به درد میخوره. Done یه کاناله که وقتی کانتکست تموم شد (مثلاً به خاطر تایمآوت یا لغو شدن)، بسته میشه و گوروتینها سیگنال میگیرن که "وقت رفتنه!".
#گولنگ #گو #go #golang
Please open Telegram to view this post
VIEW IN TELEGRAM
Anophel | آنوفل
آنوفل (Anophel): دنیای برنامه نویسان
آنوفل، دنیایی از امکانات برای برنامه نویسان! دورههای آموزشی متنوع، مسابقات برنامه نویسی، پادکستهای برنامهنویسی، سیستم تسکبندی و APIهای مختلف
توی این روش، کانالها یکییکی خونده میشن. یعنی اول دادههای کانال اول رو میخونیم، وقتی تموم شد میریم سراغ کانال بعدی.
این روش سادهتره ولی ممکنه زمان بیشتری بگیره چون منتظره که هر کانال بسته بشه.
مثال: تصویر 1
گه دو کانال داشته باشیم که هر کدوم ۴ عدد تولید میکنن و هر عدد ۵۰ میلیثانیه طول بکشه، زمان کلی ۳۵۰ میلیثانیه میشه (یعنی یکی بعد از دیگری).
اینجا همه کانالها همزمان خونده میشن و دادهها به کانال مقصد ارسال میشن. این روش سریعتره ولی ترتیب دادهها رو تضمین نمیکنه. معمولاً برای پروژههایی که سرعت مهمه از این روش استفاده میکنیم.
مثال : تصویر 2
با استفاده از این روش، دو کانال ما فقط ۲۰۰ میلیثانیه طول میکشن تا دادهها رو ترکیب کنن.
از روش Sequential استفاده کنید.
اگر کارایی و همزمانی اهمیت دارند: از روش Concurrent استفاده کنید.
#گو #گولنگ #go #golang #goroutines
Please open Telegram to view this post
VIEW IN TELEGRAM
یا شاید دلت بخواد یه بار فانکشنها رو آماده کنی و هر وقت خواستی دوباره اجراشون کنی؟
اینجاست که مفهوم Wrapper Types تو گولنگ میاد وسط. تو این پست، میخوام یه راه حل تمیز و شیک بهت معرفی کنم: ConcRunner
Wrapper Types چیه؟
فرض کن یه چیزی داری مثل اجرای فانکشنها به صورت همزمان (concurrently). خب، این کار خودش یه ذره پیچیدگی داره چون باید با goroutineها و sync.WaitGroup کلنجار بری. حالا ما اومدیم یه نوع جدید به اسم ConcRunner درست کردیم که این داستان رو میپیچه تو خودش. دولوپر فقط میگه «هی، این فانکشنهام رو بگیر و همزمان اجراشون کن»، دیگه نمیپرسه چطور این کار انجام میشه.
مثال تصویر 1
سادگی در استفاده: دیگه کسی لازم نیست نگران goroutine و sync.WaitGroup باشه.
قابلیت استفاده مجدد: فانکشنها رو هر چند بار که بخوای میتونی اضافه و اجرا کنی.
محافظت از جزئیات: کل سینک شدن و داستانهای پشت پرده رو میسپری به ConcRunner، تمیز و بیدردسر.
#گو #گولنگ #go #golang
Please open Telegram to view this post
VIEW IN TELEGRAM
یه راه خفن برای کنترل این داستان استفاده از چیزی به اسم Semaphore هست. اینجوری میتونی تعداد گوروتینهای در حال اجرا رو محدود کنی.
1. یه کانال با ظرفیت مشخص (N) درست میکنی که این ظرفیت میشه تعداد گوروتینهای همزمانی که میخوای اجرا بشه.
2. کانال رو با N تا "توکن" (هرچیزی مثل عدد) پر میکنی.
3. هر گوروتین قبل از اجرا باید یه توکن از کانال بگیره و وقتی کارش تموم شد، توکن رو برمیگردونه.
4. اگه توکن نباشه، گوروتین منتظر میمونه تا یکی آزاد بشه.
کد داخل تصویر یه مثال ساده با N=2 هست.
#Golang #go #گو #گولنگ
Please open Telegram to view this post
VIEW IN TELEGRAM
بیاید مثال داخل تصویر رو بررسی کنیم.
اینجا فرستنده منتظر گیرنده نمیمونه، ولی اگه تعداد دادهها بیشتر از ظرفیت بافر بشه چی؟ آیا باید اندازه بافر رو زیاد کنیم یا ی استراتژی دیگه به کار ببریم؟
#گولنگ #گو #بهینهسازی_کد #Go
Please open Telegram to view this post
VIEW IN TELEGRAM
Anophel | آنوفل
💠چندتا گیرنده همزمان (Worker Pool)
وقتی کار زیاده، چرا یه گیرنده داشته باشیم؟ میتونیم چند گوروتین گیرنده راه بندازیم تا موازی کار کنن. مثلاً:
stream := make(chan int, 5)
go func() {
for i := 1; i <= 10; i++ { fmt.Println("فرستنده:", i)
stream <- i }
close(stream) // کانال بسته میشه}()
for i := 0; i < 3; i++ { go func(id int) {
for data := range stream { fmt.Printf("گیرنده %d: داده %d پردازش شد\n", id, data)
} }(i)
}
با این روش چند تا گوروتین دادهها رو به صورت همزمان میگیرن و پردازش میکنن.
💠کنترل فشار لحظهای (Backpressure)
اگه نمیخوایم سیستم پر بشه و فرستنده هی داده بریزه، کانالهای بدون بافر میتونن کمک کنن. فرستنده منتظر میمونه و فشار کنترل میشه.
💠محدود کردن نرخ ارسال (Rate Limiter)
وقتی گیرندهها نمیتونن سریع پردازش کنن، میتونیم سرعت فرستنده رو محدود کنیم. با استفاده از time.Ticker خیلی راحت میشه این کارو انجام داد:
rateLimiter := time.Tick(100 * time.Millisecond)
go func() { for i := 1; i <= 10; i++ {
<-rateLimiter fmt.Println("فرستنده:", i)
stream <- i }
close(stream)}()
💠صفهای پیشرفته (Priority Queue)
اگه اولویتبندی مهمه، میتونیم از صفهای اولویتدار استفاده کنیم.
⭐️آخرش چی؟
همه چی به نیاز پروژه بستگی داره:
اگه بار بالاست و توزیع شده، از worker pool استفاده کن.
اگه نمیخوای سیستم منفجر بشه، کانالهای بدون بافر و محدود کردن نرخ ارسال جواب میده.
برای نیازهای خاص مثل اولویتبندی، ابزارهای پیشرفتهتر لازم داری.
#گولنگ #گو
#بهینهسازی_کد #go #Golang@anophel
Please open Telegram to view this post
VIEW IN TELEGRAM
حالا جذابیتش چیه؟ هر مرحله، یه مسئولیت خاص داره و میشه به راحتی تغییرش داد، کم یا زیادش کرد، یا حتی تو پروژههای دیگه استفادهش کرد.
تو یه پخش زنده، ممکنه دادهها این شکلی پردازش بشن:
این مدل به راحتی قابل توسعه و سفارشیسازی هست.
توضیح مراحل:
rangeGen: اعداد رو در بازه مشخص تولید میکنه.
takeLucky: فقط اعداد خوششانس (قابل تقسیم بر 7 ولی نه بر 13) رو انتخاب میکنه.
merge: دادههای خروجی از چند کانال مستقل رو یکی میکنه.
sum: جمع اعداد خوششانس و تعدادشون رو محاسبه میکنه.
printTotal: نتایج رو چاپ میکنه.
راستی، تا حالا تو پروژههاتون از همچین روشی استفاده کردین؟
خوشحال میشم تجربههاتون رو بشنوم.
#گو #گولنگ #go #golang
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
ولی خب، چون احتمال داره عدد زوج تولید بشه، پانیک میخوریم و برنامه میترکه!
برای مدیریت این خطا، اولین ایده ممکن اینه که یه recover تو گوروتین اصلی بذاریم حالا اگر بیاییم و یه recover توی گوروتین اصلی بیذاریم، ولی بازم پانیک میکنه! چرا؟
چون recover فقط توی همون گوروتینی جواب میده که خطا توش اتفاق افتاده. اینجا خطاها توی گوروتینهای کارگر اتفاق میفته، ولی ما داریم توی گوروتین اصلی recover رو صدا میزنیم. (گوروتین ها مستقل از هم دیگه هستن!)
اینجوری هر گوروتین خودش خطای خودش رو مدیریت میکنه و توی گوروتین اصلی متوجه میشیم که همه چیز اوکی بود یا نه.
#گو #گولنگ #Go #golang
Please open Telegram to view this post
VIEW IN TELEGRAM
Forwarded from DevTwitter | توییت برنامه نویسی
چطوری به متدهای Policy توی لاراول متغیر ارسال کنیم؟
داخل متد authorize کلاس Gate بعد از اینکه اسم متد متناظر توی policy رو پاس دادیم در قالب یک آرایه، اول مدلمون رو پاس میدیم و بعد متغیری که مدنظرمون هست. اینجوری میتونیم اون متغیر رو داخل متد policy داشته باشیم. مثال:
توی عکس های زیر، من میخوام آرایه ای از آیدی های کاربران رو بگیرم و همشون رو حذف کنم، اما میخوام در صورتی که آیدی شماره ۱ بین آیدی های انتخاب شده باشه، اجازه حذف داده نشه و خطای ۴۰۳ برگردونه. پس آرایه آیدی هارو پاس میدم به policy و اونجا چک میکنم، اگر آیدی ۱ داخل آرایه نبود، true برگردونه و اجازه عملیات رو صادر کنه.
عکس اول داخل کنترلر جایی که از Gate پالیسی رو فرا میخونیم. و عکس دوم داخل UserPolicy هست. (نحوه فراخونی policy توی لاراول ۱۱ کمی عوض شده)
@DevTwitter | <Pouya Farshidnia/>
داخل متد authorize کلاس Gate بعد از اینکه اسم متد متناظر توی policy رو پاس دادیم در قالب یک آرایه، اول مدلمون رو پاس میدیم و بعد متغیری که مدنظرمون هست. اینجوری میتونیم اون متغیر رو داخل متد policy داشته باشیم. مثال:
توی عکس های زیر، من میخوام آرایه ای از آیدی های کاربران رو بگیرم و همشون رو حذف کنم، اما میخوام در صورتی که آیدی شماره ۱ بین آیدی های انتخاب شده باشه، اجازه حذف داده نشه و خطای ۴۰۳ برگردونه. پس آرایه آیدی هارو پاس میدم به policy و اونجا چک میکنم، اگر آیدی ۱ داخل آرایه نبود، true برگردونه و اجازه عملیات رو صادر کنه.
عکس اول داخل کنترلر جایی که از Gate پالیسی رو فرا میخونیم. و عکس دوم داخل UserPolicy هست. (نحوه فراخونی policy توی لاراول ۱۱ کمی عوض شده)
@DevTwitter | <Pouya Farshidnia/>
مثلاً فرض کن یه کدی داریم که باید یه تابع به اسم ()work رو اجرا کنه. این تابع قراره 100 میلیثانیه طول بکشه. حالا دو تا سناریو داریم:
اینجا Deadline رو 150 میلیثانیه تعیین میکنیم، یعنی کد ما زمان کافی داره.
func main() {
deadline := time.Now().Add(150 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
res, err := execute(ctx, work)
fmt.Println(res, err) // نتیجه درست برمیگرده
}
کد بدون مشکل اجرا میشه چون زمان کافی داشتیم.
حالا Deadline رو 50 میلیثانیه میذاریم، اما ()work حداقل 100 میلیثانیه نیاز داره.
func main() {
deadline := time.Now().Add(50 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
res, err := execute(ctx, work)
fmt.Println(res, err) // نتیجه لغو میشه
}
عملیات لغو میشه (
context.Canceled
) چون مهلت کافی وجود نداشت. ithTimeout
و WithDeadline
چیه؟بله WithTimeout یه مقدار زمان مشخص میگیره (مثلاً 5 ثانیه)، اماWithDeadline دقیقاً یه زمان مشخص (مثلاً 23:00:05).
جالبیش اینه که WithTimeout خودش از WithDeadline استفاده میکنه:
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
با متد Deadline، میتونیم زمان مشخص شده رو ببینیم.
اگه کانتکست با WithTimeout یا WithDeadline شده باشه، یه زمان مشخص میده.
اما اگه با WithCancel یا Background ساخته شده باشه، میگه نه!
ctx, _ := context.WithCancel(context.Background())
deadline, ok := ctx.Deadline()
fmt.Println(deadline, ok) // نتیجه: false
ctx = context.Background()
deadline, ok = ctx.Deadline()
fmt.Println(deadline, ok) // باز هم false
#گو #گولنگ #Go #Golang
Please open Telegram to view this post
VIEW IN TELEGRAM
اگه شما هم با Golang کد میزنید، خبر خوب اینه که ورژن 1.24 قراره تو فوریه 2025 منتشر بشه و کلی بهبود خفن آورده که کارمون رو هم سریعتر میکنه، هم راحتتر.
گولنگ با این تغییر، مدیریت مپها رو خیلی بهینهتر کرده! رم کمتر مصرف میشه و عملیات خوندن/نوشتن سریعتر شده.
دو پکیج جدید داریم:
اولی os.Root برای راحتتر کردن کارهای مربوط به دایرکتوریها.
دومی weak برای هندل کردن weak pointers که خیلی وقتا به درد میخوره.
یه قابلیت جدید برای alias کردن sliceها که به خصوص با افزایش استفاده از جنریکها خیلی کاربردیه.
یه قابلیت خفن به اسم متا-پترن اضافه شده که نصب ابزارهایی مثل mockgen رو راحتتر از همیشه میکنه.
خروجی JSON برای go test: تحلیل تستها دیگه یه لذت شده!
و GOAUTH: حالا میتونید راحتتر به ریپوزیتوریهای پرایوت مثل GitHub وصل بشید.
این نسخه شاید تغییرات انقلابی نداشته باشه، ولی پر از آپدیتهای کاربردی که واقعاً حس میکنید کارتون سریعتر و تمیزتر شده. تغییراتی مثل سوئیچ به SwissTable و GOAUTH کار رو برای تیمهایی که با پروژههای بزرگ و پرایوت کار میکنن، بسیار سادهتر کرده.
#گولنگ #گولنگ_1_24
#GoLang #Go1_24 #DeveloperExperience #Tooling
Please open Telegram to view this post
VIEW IN TELEGRAM
فرض کن جلوی یه ساختمون خیلی شیک و بزرگ وایسادی، ولی لازم نیست بری داخلش و همه اتاقاشو ببینی. فقط کافیه از بیرون، نمای ساده و مرتبش رو ببینی و کارتو راه بندازی. پترن Facade دقیقاً همینه! یه رابط کاربری ساده میده که دیگه لازم نباشه با پیچیدگیهای سیستم درگیر بشی.
دو تا اصل مهم دنیای کدنویسی اینجا برامون روشنتر میشه:
وقتی از Facade استفاده میکنی، پیچیدگیهای پشت پرده سیستم رو قایم میکنی و فقط چیزی که کاربر (یا حتی خودت!) نیاز داری رو نشون میدی. کدت مرتبتر، قابل نگهداریتر و قابلفهمتر میشه.
فرض کن میخوای به دیتابیس وصل بشی، کوئری بزنی و قطع کنی. بدون Facade اینجوریه:
type Database struct {
DatabaseConnection *sql.DB
}
func (db *Database) Connect() {
// کد اتصال
}
func (db *Database) Query() {
// کد کوئری
}
func (db *Database) Disconnect() {
// قطع اتصال
}
// استفاده
var db Database
db.Connect()
db.Query()
db.Disconnect()
ولی با Facade، همه اینا تو یه دونه تابع جمع میشه:
type DatabaseFacade struct {
Database *Database
}
func (dbf *DatabaseFacade) ExecuteQuery() {
dbf.Database.Connect()
dbf.Database.Query()
dbf.Database.Disconnect()
}
// استفاده
var dbf DatabaseFacade
dbf.ExecuteQuery()
کار با فایلها
ارتباط با API
راهاندازی وبسرور
مدیریت سفارشهای فروشگاه آنلاین
دیزاین پترن Facade، یه ابزار فوقالعاده برای سادهسازی و مرتب کردن کدهاتونه. اما همونطور که هر چیزی حد داره، از این پترن هم فقط وقتی استفاده کن که به درد بخوره و لازم باشه.
شما چطور از Facade استفاده کردید تجربههاتون رو تو کامنتها بگید.
#الگوی_طراحی #Facade #گو #گولنگ #Go #Golang #KISS #DRY
Please open Telegram to view this post
VIEW IN TELEGRAM
خیلیها این روزها دوباره دارن از سرورلس به سمت سرورهای اختصاصی و VPS برمیگردن. چند سال پیش همه دنبال سرورلس بودن، ولی الان؟ دیگه اوضاع فرق کرده! توی این پست میخوام چند تا از دلایلی که سرورلس یه انتخاب ضعیفه رو به زبون ساده باهاتون به اشتراک بذارم.
شرکتهای بزرگی مثل AWS و Azure دقیقاً میدونن چطوری شما رو قفل سیستم خودشون کنن. وقتی واردش میشید، بیرون اومدن ازش هزینههای وحشتناک داره:
نتیجه؟ یه قبض ماهانه که هیچی ازش نمیفهمید ولی قطعاً خوشحالتون نمیکنه.
بهقول یکی از مدیران بزرگ، اومدن روی VPS میتونه هزاران دلار صرفهجویی کنه. تازه، وقتی خودت سیستم رو مدیریت میکنی، دستت بازتره و همهچی رو کنترل میکنی.
سرورلس بهشدت پیچیدهست. مثال ساده؟ یه کانفیگ اشتباه میتونه میلیاردها تومان خسارت بزنه!
بودند کسایی که بهخاطر فراموش کردن تنظیمات S3، هزینههای سنگینی دادن!
توی VPS، همهچی رو میتونی ساده و شفاف مدیریت کنی. مثلاً یه آموزش ساده برای قفل کردن سرور با Fail2Ban و غیرفعال کردن رمز ورود برای کاربر Root میتونه امنیت رو بهشدت بالا ببره.
وقتی سیستم سادهتر باشه، کمتر قطع میشه. از وقتی از سرورلس به VPS مهاجرت کردم، سرویسهام هیچوقت خودبهخود قطع نشدن. ولی سرورلس؟
آپتایم ادعایی این شرکتها معمولاً روی کاغذه. ولی واقعیت؟ یه اشتباه کوچیک، یه کلیک اشتباه و تمام اون ۹۹.۹۹٪ میپره.
با سرورلس، هر سرویس جداگانه باید کانفیگ بشه. برای هر میکروسرویس باید دستی کانفیگ امنیتی انجام بدی. این یعنی کار چند برابر.
ولی روی VPS؟ یه سرور، یه کانفیگ، تمومه!
بعضیها میگن سرورلس بهتون محافظت رایگان DDoS میده. اما یه نکتهی جالب:
شما همون محافظت رو میتونید با Cloudflare بهصورت رایگان بگیرید. کافیه پروکسی Cloudflare رو فعال کنید و SSL/TLS رو روی حالت Full بذارید. همون یه کلیک، همون امنیت. نیازی به سرورلس نیست.
#سرورلس #میکروسرویس #serverless #vps
Please open Telegram to view this post
VIEW IN TELEGRAM
func send(ch chan<- int) {
defer close(ch)
fmt.Println("Sending 1...")
ch <- 1 // Blocks until the receiver is ready
for i := 0; i < 10; i++ {
fmt.Printf("Sending %d...\n", i)
ch <- i // Blocks until the receiver reads the value
}
}
func main() {
ch := make(chan int) // Unbuffered channel
go send(ch)
for val := range ch {
fmt.Printf("Received %d\n", val)
}
}
این رفتار به دلیل ماهیت همزمانی goroutineها است:
در فانکشن send، خط fmt.Println("Sending 0...") قبل از بلوکه شدن ch <- i اجرا میشه. یعنی حتی اگه فرستادن داده منتظر گیرنده بمونه، متن چاپ میشه.
گیرنده در حلقه for در حال دریافت و چاپ داده است. اما عملیات ارسال و دریافت روی دو goroutine مختلف اجرا میشن. این یعنی ترتیب چاپها کاملاً قابل پیشبینی نیست.
گولنگ زمانبندی goroutineها رو خودش مدیریت میکنه. به همین دلیل ممکنه چاپ متن ارسال قبل از دریافت داده تموم بشه.
#گو #گولنگ #Go #Golang
Please open Telegram to view this post
VIEW IN TELEGRAM