
زبان Go از ابتدا با هدف سادهسازی ساخت نرمافزارهای مقیاسپذیر و پرکاربرد طراحی شد. یکی از مهمترین مؤلفههایی که این زبان را از سایر زبانها متمایز میکند، Goroutine است؛ سازوکاری بسیار سبکوزن برای اجرای همزمان (Concurrency) که به شکل مستقیم توسط runtime خود Go مدیریت میشود.
در این مقاله، به ساختار Goroutine، نحوه اجرای آن در runtime، رابطهاش با Scheduler، تفاوت آن با Threadهای سیستمعامل و نکات کلیدی در استفاده حرفهای از آن میپردازیم.
گوروتین یک واحد اجرای همزمان در Go است که بهصورت بسیار سبکوزن ایجاد و مدیریت میشود. برخلاف Threadهای معمولی سیستمعامل که ایجاد آنها هزینه بالایی دارد (Memory + Context Switching)، گوروتینها با مصرف حافظه بسیار کم (در حد چند کیلوبایت) ساخته میشوند و در صورت نیاز بهطور خودکار رشد یا کاهش مییابند.
به طور ساده:
Thread = سنگین، هزینهبر، وابسته به سیستمعامل
Goroutine = سبک، سریع، مدیریتشده توسط Go runtime
Go از یک معماری M:N Scheduler استفاده میکند که در آن:
M = تعداد Threadهای سیستمعامل
N = تعداد Goroutineها
Scheduler گوروتینها را روی Threadهای سیستمعامل قرار میدهد و بدون دخالت برنامهنویس، عملیات زمانبندی، تعویض context و مدیریت دورهای را تنظیم میکند.
سه مفهوم کلیدی در Scheduler عبارتاند از:
کدی که باید اجرا شود.
Thread سیستمعاملی که واقعاً اجرا را انجام میدهد.
منبع منطقی برای اجرای Goroutineها که شامل Run Queue است.
وجود Processorها به runtime کمک میکند تا load balancing و work stealing را بهتر مدیریت کند.
ساخت یک Goroutine بسیار ساده است؛ تنها کافی است قبل از فراخوانی تابع، کلمه کلیدی go را قرار دهید:
func main() { go doSomething() println("Main finished") } func doSomething() { println("Hello from goroutine") }
نکات:
اجرای main بلافاصله ادامه پیدا میکند.
اگر main تمام شود، تمام گوروتینهای دیگر نیز خاتمه مییابند.
مدیریت همزمانی بین گوروتینها نیازمند سازوکارهایی مانند Channel یا sync است.
برخلاف Threadها که معمولاً Stack با حجم 1MB یا بیشتر دارند، گوروتینها با یک Stack کوچک و داینامیک (حدود 2KB) شروع میشوند.
زمانی که حجم Stack مورد نیاز بیشتر شود، runtime آن را دوبرابر میکند. این کاهش هزینه اولیه و مدیریت پویا، دلیل اصلی سبکوزنی گوروتینهاست.

در عمل، میتوان میلیونها گوروتین را روی چند Thread سیستمعامل اجرا کرد.
برای هماهنگسازی و انتقال داده بین گوروتینها، زبان Go از سازوکاری قدرتمند به نام Channel استفاده میکند که الگوی CSP (Communicating Sequential Processes) را پیادهسازی میکند.
نمونه ساده:
func main() { ch := make(chan string) go func() { ch <- "message from goroutine" }() msg := <-ch println(msg) }
اگر گوروتین منتظر یک channel است اما هیچگاه دادهای به آن ارسال نمیشود، هرگز متوقف نخواهد شد.
از contextها یا timeoutها برای مدیریت طول عمر گوروتین استفاده کنید.
این روش استاندارد برای صبر کردن جهت پایان گوروتینهاست.
var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() doWork() }() wg.Wait()
اگرچه گوروتینها سبک هستند، اما بیبرنامه ایجاد کردن تعداد زیاد، ممکن است باعث فشار روی حافظه و Scheduler شود.
برای جلوگیری از تولید بیش از حد گوروتین، الگوی Worker Pool را بهکار ببرید.
استفاده از مقادیر loop variable در گوروتینها
فراموش کردن close کردن channelها
متوقف نشدن گوروتینهای بلاکشده
عدم مدیریت context در سرویسهای شبکهای
Goroutine یکی از کلیدیترین ویژگیهای زبان Go است که امکان ساخت اپلیکیشنهای مقیاسپذیر، سریع و همزمان را با هزینه بسیار کم فراهم میکند. معماری scheduler سبک، مدیریت خودکار Stack، و یکپارچگی با Channelها باعث شده Go یکی از بهترین انتخابها برای سیستمهای توزیعشده، سرویسهای وب، پردازش همزمان و میکروسرویسها باشد.