جواب کوتاه: goroutine یک ریسمان اجرا در سطح خودِ runtime گو است، نه سطح سیستمعامل — استک اولیهاش چیزی حدود ۲ کیلوبایت است و رشد میکند، در برابر یک تا چند مگابایت برای هر thread سیستمعامل. برای همین میشود صدها هزار goroutine باز کرد، ولی همان عدد thread، عملاً سیستم را از پا درمیآورد.
صدهزار goroutine، بدون تنظیم خاصی
govar wg sync.WaitGroup
const n = 100000
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
defer wg.Done()
}()
}
wg.Wait()
این برنامه بدون خطا و بدون تنظیم هیچ پارامتری تمام میشود. runtime گو خودش goroutineها را روی تعداد کمی OS thread (بر اساس GOMAXPROCS، معمولاً برابر تعداد هستهها) زمانبندی میکند — این همان چیزی است که به آن M:N scheduling میگویند: میلیونها goroutine (M) روی تعداد کمی thread واقعی (N).
سبکبودن، امنیت در برابر data race نمیدهد
gocounter := 0
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter++
}()
}
wg.Wait()
با پرچم -race:
$ go run -race race.go
WARNING: DATA RACE
Read at 0x00c000114018 by goroutine 7:
main.main.func1() race.go:12
Previous write at 0x00c000114018 by goroutine 5:
main.main.func1() race.go:12
هزار goroutine همزمان روی یک متغیر counter++ مینویسند — که خودش سه عملیات جدا است (خواندن، جمعزدن، نوشتن) — و بدون هماهنگی، دو goroutine میتوانند همزمان همان مقدار قدیمی را بخوانند و یکی از افزایشها گم شود. go run -race دقیقاً همین تصادم را ردیابی و گزارش میکند.
راه درست: sync.Mutex یا کانال
govar mu sync.Mutex
counter := 0
go func() {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}()
یا با کانال، بدون قفل صریح: هر goroutine نتیجهاش را در یک کانال میفرستد و یک goroutine جمعکننده تکبهتک میخواند. فلسفهی گو همین است: «بهجای اشتراک حافظه برای ارتباط، ارتباط بگیرید تا حافظه را به اشتراک بگذارید.»
چهوقت واقعاً به thread نیاز دارید
تقریباً هیچوقت مستقیم — مگر کد C را با cgo صدا بزنید که خودش thread سیستمعامل میخواهد. برای همهی همزمانی معمولی برنامه، goroutine همان چیزی است که باید استفاده کنید.
روی چند thread واقعی زمانبندی شدند؟
gofmt.Println("NumCPU:", runtime.NumCPU())
fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0))
روی ماشینی که این مثالها رویش اجرا شد: NumCPU: 11، GOMAXPROCS: 11. یعنی همان صدهزار goroutineی مثال اول، روی فقط ۱۱ thread واقعی سیستمعامل زمانبندی شدند — نه صدهزار thread. GOMAXPROCS سقف تعداد threadهایی است که همزمان کد گو را اجرا میکنند؛ پیشفرضش برابر تعداد هستههاست و بهندرت لازم است دستی عوضش کنید.
اگر میخواهید Go را با پروژهی واقعی و بکاند یاد بگیرید، دورهی Go همین الگوهای همزمانی را قدمبهقدم تمرین میدهد.





نظرها
اولین نفری باشید که نظر میدهدسؤالتان را بپرسید یا تجربهتان را بنویسید — نویسندهی مطلب پاسخ میدهد.