گوروتین چیست؟ آموزش Goroutine و همزمانی در گولنگ
2025/11/24یکی از قدرتمندترین ویژگیهای Go، پشتیبانی داخلی از برنامهنویسی همزمان است. در این مقاله، Goroutineها را به صورت کامل بررسی میکنیم و یاد میگیریم چگونه برنامههای همزمان بنویسیم.
Goroutine چیست؟
Goroutine یک thread سبکوزن است که توسط Go runtime مدیریت میشود. برخلاف threadهای سیستمعامل که سنگین هستند، Goroutineها:
- حافظه اولیه کمی مصرف میکنند (~2KB در مقابل ~1MB برای thread)
- ایجاد و تخریب سریع دارند
- توسط Go scheduler مدیریت میشوند
- میتوانید میلیونها Goroutine همزمان داشته باشید
اولین Goroutine
برای ایجاد Goroutine، کافی است کلمه go را قبل از فراخوانی تابع بگذارید:
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("سلام از Goroutine!")
}
func main() {
go sayHello() // اجرا در Goroutine جدید
// صبر کنیم تا Goroutine اجرا شود
time.Sleep(100 * time.Millisecond)
fmt.Println("سلام از main!")
}خروجی:
سلام از Goroutine!
سلام از main!نکته مهم: اگر
time.Sleepرا حذف کنید، برنامه قبل از اجرای Goroutine تمام میشود!
Goroutine با تابع ناشناس
package main
import (
"fmt"
"time"
)
func main() {
// Goroutine با تابع ناشناس
go func() {
fmt.Println("تابع ناشناس در Goroutine")
}()
// Goroutine با پارامتر
message := "سلام"
go func(msg string) {
fmt.Println(msg)
}(message)
time.Sleep(100 * time.Millisecond)
}چند Goroutine همزمان
package main
import (
"fmt"
"time"
)
func worker(id int) {
fmt.Printf("Worker %d شروع کرد\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d تمام شد\n", id)
}
func main() {
// ایجاد 5 worker همزمان
for i := 1; i <= 5; i++ {
go worker(i)
}
// صبر برای اتمام همه
time.Sleep(2 * time.Second)
fmt.Println("همه worker ها تمام شدند")
}خروجی (ترتیب ممکن است متفاوت باشد):
Worker 5 شروع کرد
Worker 1 شروع کرد
Worker 3 شروع کرد
Worker 2 شروع کرد
Worker 4 شروع کرد
Worker 1 تمام شد
Worker 5 تمام شد
Worker 2 تمام شد
Worker 3 تمام شد
Worker 4 تمام شد
همه worker ها تمام شدندWaitGroup - صبر برای اتمام Goroutineها
استفاده از time.Sleep روش مناسبی نیست. از sync.WaitGroup استفاده کنید:
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // اعلام اتمام کار
fmt.Printf("Worker %d شروع کرد\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d تمام شد\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1) // افزایش شمارنده
go worker(i, &wg)
}
wg.Wait() // صبر تا شمارنده صفر شود
fmt.Println("همه کارها تمام شد!")
}Channel - ارتباط بین Goroutineها
Channelها راه اصلی ارتباط بین Goroutineها هستند:
package main
import "fmt"
func main() {
// ایجاد channel
ch := make(chan string)
// ارسال در Goroutine
go func() {
ch <- "سلام از Goroutine!"
}()
// دریافت در main
message := <-ch
fmt.Println(message)
}Channel با بافر
package main
import "fmt"
func main() {
// Channel با ظرفیت 3
ch := make(chan int, 3)
// ارسال بدون بلاک شدن (تا ظرفیت)
ch <- 1
ch <- 2
ch <- 3
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
fmt.Println(<-ch) // 3
}بستن Channel
package main
import "fmt"
func producer(ch chan int) {
for i := 1; i <= 5; i++ {
ch <- i
}
close(ch) // اعلام پایان ارسال
}
func main() {
ch := make(chan int)
go producer(ch)
// دریافت تا بسته شدن channel
for num := range ch {
fmt.Println(num)
}
}مثال عملی: دانلود همزمان
package main
import (
"fmt"
"io"
"net/http"
"sync"
"time"
)
type Result struct {
URL string
Size int
Error error
}
func fetchURL(url string, results chan<- Result, wg *sync.WaitGroup) {
defer wg.Done()
start := time.Now()
resp, err := http.Get(url)
if err != nil {
results <- Result{URL: url, Error: err}
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
results <- Result{URL: url, Error: err}
return
}
elapsed := time.Since(start)
fmt.Printf("%s: %d bytes in %v\n", url, len(body), elapsed)
results <- Result{URL: url, Size: len(body)}
}
func main() {
urls := []string{
"https://golang.org",
"https://google.com",
"https://github.com",
}
results := make(chan Result, len(urls))
var wg sync.WaitGroup
start := time.Now()
for _, url := range urls {
wg.Add(1)
go fetchURL(url, results, &wg)
}
// بستن channel بعد از اتمام همه
go func() {
wg.Wait()
close(results)
}()
// جمعآوری نتایج
totalSize := 0
for result := range results {
if result.Error == nil {
totalSize += result.Size
}
}
fmt.Printf("\nTotal: %d bytes in %v\n", totalSize, time.Since(start))
}Select - انتخاب بین چند Channel
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(100 * time.Millisecond)
ch1 <- "پیام از channel 1"
}()
go func() {
time.Sleep(200 * time.Millisecond)
ch2 <- "پیام از channel 2"
}()
// دریافت از هر کدام که زودتر آماده شود
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
}Select با Timeout
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch <- "پاسخ"
}()
select {
case msg := <-ch:
fmt.Println("دریافت:", msg)
case <-time.After(1 * time.Second):
fmt.Println("Timeout!")
}
}الگوی Worker Pool
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
fmt.Printf("Worker %d پردازش job %d\n", id, job)
time.Sleep(500 * time.Millisecond) // شبیهسازی کار
results <- job * 2
}
}
func main() {
numJobs := 10
numWorkers := 3
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
// ایجاد worker ها
for w := 1; w <= numWorkers; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// ارسال کارها
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// صبر و بستن results
go func() {
wg.Wait()
close(results)
}()
// جمعآوری نتایج
for result := range results {
fmt.Println("نتیجه:", result)
}
}Mutex - جلوگیری از Race Condition
وقتی چند Goroutine به داده مشترک دسترسی دارند:
package main
import (
"fmt"
"sync"
)
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}
func main() {
counter := &Counter{}
var wg sync.WaitGroup
// 1000 Goroutine همزمان
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait()
fmt.Println("مقدار نهایی:", counter.Value()) // همیشه 1000
}تفاوت Goroutine با Thread
| ویژگی | Goroutine | Thread OS |
|---|---|---|
| حافظه اولیه | ~2KB | ~1MB |
| ایجاد | میکروثانیه | میلیثانیه |
| مدیریت | Go runtime | سیستمعامل |
| ارتباط | Channel | Shared memory |
| تعداد | میلیونها | هزاران |
| Context switch | سریع | کند |
نکات مهم
۱. همیشه Goroutineها را مدیریت کنید
// بد - Goroutine leak
func bad() {
go func() {
for {
// کار بیپایان
}
}()
}
// خوب - با امکان توقف
func good(done chan bool) {
go func() {
for {
select {
case <-done:
return
default:
// کار
}
}
}()
}۲. از Race Detector استفاده کنید
go run -race main.go
go test -race ./...۳. پارامترها را صریح پاس دهید
// بد - مشکل closure
for i := 0; i < 5; i++ {
go func() {
fmt.Println(i) // همیشه 5 چاپ میشود!
}()
}
// خوب - پاس دادن مقدار
for i := 0; i < 5; i++ {
go func(n int) {
fmt.Println(n) // 0, 1, 2, 3, 4
}(i)
}جمعبندی
| مفهوم | کاربرد |
|---|---|
go func() |
ایجاد Goroutine |
sync.WaitGroup |
صبر برای اتمام |
chan |
ارتباط بین Goroutineها |
select |
انتخاب بین channelها |
sync.Mutex |
محافظت از داده مشترک |
قدمهای بعدی
- Channel در Go - بررسی عمیقتر
- الگوهای همزمانی
- Context در Go