تفاوت goroutine و Thread سیستم‌عامل در Go
آموزش

تفاوت goroutine و Thread سیستم‌عامل در Go

یک goroutine خیلی سبک‌تر از یک thread سیستم‌عامل است، ولی سبک‌بودن concurrency را رایگان نمی‌کند؛ تفاوت را با اجرای واقعی ببین

تحریریه‌ی دینا کد
نویسنده
۴ دقیقه مطالعه۳ بازدید
اشتراک گذاری۳
فهرست مطالب
  1. ۱صدهزار goroutine، بدون تنظیم خاصی
  2. ۲سبک‌بودن، امنیت در برابر data race نمی‌دهد
  3. ۳راه درست: sync.Mutex یا کانال
  4. ۴چه‌وقت واقعاً به thread نیاز دارید
  5. ۵روی چند thread واقعی زمان‌بندی شدند؟

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 همین الگوهای همزمانی را قدم‌به‌قدم تمرین می‌دهد.

تحریریه‌ی دینا کد
نویسنده

نظرها

اولین نفری باشید که نظر می‌دهد
برای ثبت نظر وارد شوید

سؤالتان را بپرسید یا تجربه‌تان را بنویسید تا نویسنده‌ی مطلب پاسخ بدهد.

مطالب مرتبط

از همین دسته
مدیریت خطا با try و except در پایتون
آموزش

مدیریت خطا با try و except در پایتون

راهنمای گام‌به‌گام مهار خطاهای زمان اجرا در پایتون با بلوک‌های try و except همراه با بررسی خطاهای رایج و ساختار اصولی.

تحریریه‌ی دینا کد۵ دقیقه
مدیریت خطا در Rust با Result و Option
آموزش

مدیریت خطا در Rust با Result و Option

زبان Rust به جای ساختارهای سنتی استثنا از نوع‌های Result و Option برای مدیریت شفاف و پیش‌بینی‌پذیر خطاها در زمان کامپایل استفاده می‌کند.

تحریریه‌ی دینا کد۶ دقیقه
تفاوت واحدهای px و em و rem در CSS و کاربرد هرکدام
آموزش

تفاوت واحدهای px و em و rem در CSS و کاربرد هرکدام

واحدهای px، em و rem هرکدام برای بخش مشخصی از صفحه‌آرایی وب طراحی شده‌اند و انتخاب نادرست آن‌ها مقیاس‌پذیری و دسترسی‌پذیری سایت را مختل می‌کند.

تحریریه‌ی دینا کد۶ دقیقه