فهرست مطالب
goroutine ریسمان اجرای خودِ runtime گو است، نه thread سیستمعامل. استک اولیهاش حدود ۲ کیلوبایت است و با نیاز برنامه رشد میکند، در حالی که هر 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 دقیقاً همین تصادم را ردیابی و گزارش میکند. (این خروجی نمونه است؛ شمارهی goroutine و آدرسها در هر اجرا فرق میکنند.)
راه درست: sync.Mutex یا کانال
govar mu sync.Mutex
counter := 0
go func() {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}()
یا با کانال، بدون قفل صریح: هر goroutine نتیجهاش را در یک کانال میفرستد و یک goroutine جمعکننده تکبهتک میخواند. فلسفهی گو همین است: «بهجای اشتراک حافظه برای ارتباط، ارتباط بگیرید تا حافظه را به اشتراک بگذارید.»
چهوقت واقعاً به thread نیاز دارید
تقریباً هیچوقت به آن نیاز مستقیم ندارید؛ تنها استثنا وقتی است که کد C را با cgo صدا بزنید، چون 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 همین الگوهای همزمانی را قدمبهقدم تمرین میدهد.




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