راهنمای سریع و کاربردی Concurrency در Golang


۰


Concurrency یکی از قدرتمندترین ویژگی‌های زبان Go است که به شما امکان می‌دهد برنامه‌هایی بسیار کارآمد و مقیاس‌پذیر بنویسید. در این چیت‌شیت، اصول اولیه‌ی concurrency در Go — مانند goroutine‌ها، channel‌ها، sync.WaitGroup، mutex‌ها و موارد دیگر — به همراه مثال‌های کاربردی کد پوشش داده شده است. چه تازه‌کار باشید و چه بخواهید یک مرجع سریع داشته باشید، این راهنما برای شماست!

فهرست مطالب

  1. Goroutineها
  2. Channelها
  3. WaitGroup
  4. Mutex و RWMutex
  5. دستور Select
  6. Context برای لغو (Cancellation)
  7. نکات و بهترین شیوه‌ها
  8. منابع بیشتر

1. Goroutineها

Goroutine‌ها رشته‌های (thread) سبک وزنی هستند که توسط runtime زبان Go مدیریت می‌شوند. آنها امکان اجرای همزمان کارها را با کمترین سربار فراهم می‌کنند.

1package main
2
3import (
4	"fmt"
5	"time"
6)
7
8func printMessage(msg string) {
9	fmt.Println(msg)
10}
11
12func main() {
13	// شروع یک goroutine جدید
14	go printMessage("Hello from a goroutine!")
15	// زمان دادن به goroutine برای اتمام کار
16	time.Sleep(100 * time.Millisecond)
17	fmt.Println("Main function finished.")
18}

نکات کلیدی:

  • برای شروع یک goroutine از کلیدواژه go استفاده کنید.
  • Goroutine‌ها به صورت همزمان اجرا می‌شوند، اما ممکن است نیاز باشد برای همگام‌سازی (مثلاً با WaitGroup) منتظر آنها بمانید.

2. Channelها

Channel‌ها یک راه امن برای ارتباط بین goroutine‌ها فراهم می‌کنند که در آن شما می‌توانید پیام‌های تایپ دار را ارسال و دریافت کنید.

مثال پایه‌ای Channel

1package main
2import "fmt"
3
4func sum(a, b int, result chan int) {
5	result <- a + b // فرستادن حاصل جمع به channel
6}
7
8func main() {
9	result := make(chan int)
10	go sum(3, 4, result)
11	fmt.Println("Sum:", <-result) // دریافت از channel
12}

Channelهای با بافر (Buffered)

1package main
2import "fmt"
3
4func main() {
5	ch := make(chan string, 2) // ایجاد channel با بافر ظرفیت 2
6	ch <- "Hello"
7	ch <- "World"
8	// خواندن بدون بلاک شدن
9	fmt.Println(<-ch)
10	fmt.Println(<-ch)
11}

نکات کلیدی:

  • Channelهای بدون بافر ارسال‌کننده را تا زمانی که دریافت‌کننده آماده باشد، بلاک می‌کنند.
  • Channelهای با بافر امکان ارسال چند مقدار بدون دریافت‌کننده فوری را فراهم می‌کنند.

3. WaitGroup

sync.WaitGroup برای منتظر ماندن تا تمام goroutineهای مشخص شده اجرا و به اتمام برسند، استفاده می‌شود.

1package main
2
3import (
4	"fmt"
5	"sync"
6	"time"
7)
8
9func worker(id int, wg *sync.WaitGroup) {
10	defer wg.Done() // علامت پایان این goroutine
11	time.Sleep(100 * time.Millisecond)
12	fmt.Printf("Worker %d done\n", id)
13}
14
15func main() {
16	var wg sync.WaitGroup
17	// اجرای 5 worker
18	for i := 1; i <= 5; i++ {
19		wg.Add(1)
20		go worker(i, &wg)
21	}
22	// منتظر ماندن تا تمام worker‌ها پایان یابند
23	wg.Wait()
24	fmt.Println("All workers completed.")
25}

نکات کلیدی:

  • قبل از شروع n goroutine، wg.Add(n) را فراخوانی کنید.
  • هر goroutine باید در پایان با defer wg.Done() اعلام اتمام کند.
  • برای بلاک کردن تا پایان همه goroutine‌ها از wg.Wait() استفاده کنید.

4. Mutex و RWMutex

Mutexها کمک می‌کنند تا شرایط رقابتی (race condition) جلوگیری شود و مطمئن شویم که تنها یک goroutine در هر لحظه به منبع مشترک دسترسی دارد.

استفاده از Mutex

1package main
2
3import (
4	"fmt"
5	"sync"
6)
7
8func main() {
9	var mu sync.Mutex
10	counter := 0
11
12	increment := func() {
13		mu.Lock()
14		counter++
15		mu.Unlock()
16	}
17
18	var wg sync.WaitGroup
19	for i := 0; i < 1000; i++ {
20		wg.Add(1)
21		go func() {
22			defer wg.Done()
23			increment()
24		}()
25	}
26	wg.Wait()
27	fmt.Println("Counter:", counter)
28}

استفاده از RWMutex

وقتی تعداد خواننده‌ها زیاد و نویسنده‌ها کم است، از sync.RWMutex استفاده کنید.

1package main
2
3import (
4	"fmt"
5	"sync"
6)
7
8func main() {
9	var mu sync.RWMutex
10	data := make(map[string]int)
11
12	// نویسنده
13	go func() {
14		mu.Lock()
15		data["key"] = 42
16		mu.Unlock()
17	}()
18
19	// خواننده
20	mu.RLock()
21	value := data["key"]
22	mu.RUnlock()
23	fmt.Println("Value:", value)
24}

5. دستور Select

دستور select به شما اجازه می‌دهد که همزمان منتظر چند عملیات بر روی channel‌ها باشید؛ شبیه به switch اما برای channel‌ها.

1package main
2
3import (
4	"fmt"
5	"time"
6)
7
8func main() {
9	ch1 := make(chan string)
10	ch2 := make(chan string)
11
12	go func() {
13		time.Sleep(50 * time.Millisecond)
14		ch1 <- "from ch1"
15	}()
16
17	go func() {
18		time.Sleep(100 * time.Millisecond)
19		ch2 <- "from ch2"
20	}()
21
22	select {
23	case msg1 := <-ch1:
24		fmt.Println("Received:", msg1)
25	case msg2 := <-ch2:
26		fmt.Println("Received:", msg2)
27	case <-time.After(150 * time.Millisecond):
28		fmt.Println("Timeout")
29	}
30}

نکات کلیدی:

  • select بلاک می‌شود تا یکی از case‌ها آماده اجرا شدن شود.
  • می‌توانید از default برای non-blocking کردن select استفاده کنید.

6. Context برای لغو (Cancellation)

پکیج context به شما امکان می‌دهد که deadline، سیگنال‌های لغو و دیگر مقادیر مرتبط با درخواست را بین boundaryهای API رد و بدل کنید.

1package main
2
3import (
4	"context"
5	"fmt"
6	"time"
7)
8
9func longRunningTask(ctx context.Context) {
10	select {
11	case <-time.After(2 * time.Second):
12		fmt.Println("Task completed")
13	case <-ctx.Done():
14		fmt.Println("Task canceled:", ctx.Err())
15	}
16}
17
18func main() {
19	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
20	defer cancel()
21	longRunningTask(ctx)
22}

نکات کلیدی:

  • از context.WithTimeout یا context.WithCancel برای کنترل مدت عمر goroutine استفاده کنید.
  • همیشه ctx را به توابعی که پشتیبانی از لغو دارند، پاس دهید.

7. نکات و بهترین شیوه‌ها

  • اجتناب از Leak شدن Goroutine:
    همیشه اطمینان حاصل کنید goroutine‌ها می‌توانند به درستی خارج شوند، مثلاً با استفاده از contexts یا همگام‌سازی مناسب.
  • کاهش استفاده از حافظه مشترک:
    ترجیحاً داده‌ها بین goroutineها با channel‌ها به اشتراک گذاشته شود تا قفل‌های صریح.
  • استفاده از Race Detector:
    با اجرای کد خود از طریق go run -race شرایط رقابتی را در زمان توسعه پیدا کنید.
  • ساده نگه داشتن:
    با الگوهای ساده مانند WaitGroup و channel شروع کنید و سپس به سراغ هماهنگی‌های پیچیده‌تر بروید.
  • پروفایل کردن کارایی:
    با ابزارهایی مثل pprof محل‌های گلوگاه concurrency را بشناسید.

8. منابع بیشتر


این چیت‌شیت اصول concurrency در Go را با مثال‌های عملی و شیوه‌های برتر پوشش می‌دهد تا در نوشتن کدهای همزمان بهینه و امن به شما کمک کند. اگر این مطلب برایتان مفید بود، آن را به اشتراک بگذارید، نظرات خود را بگویید یا نکات بیشتر خود را اضافه کنید. کدنویسی خوش!

با دنبال کردن این راهنما، بنیان محکمی برای پیاده‌سازی و درک concurrency در Go خواهید داشت. از ساخت برنامه‌های مقیاس‌پذیر و کارآمد خود لذت ببرید!

ابزار برنامه نویسی

۰


نظرات


author
نویسنده مقاله: حمید فیض‌آبادی

کد با می متعهد است که بالاترین سطح کیفی آموزش را در اختیار شما بگذارد. هدف به اشتراک گذاشتن دانش فناوری اطلاعات و توسعه نرم افزار در بالاترین سطح ممکن برای درستیابی به جامعه ای توانمند و قدرتمند است. ما باور داریم هر کسی میتواند با استمرار در یادگیری برنامه نویسی چالش های خود و جهان پیرامون خود را بر طرف کند و به موفقیت های چشم گیر برسد. با ما در این مسیر همراه باشید. کد با می اجتماع حرفه ای برنامه نویسان ایرانی.

تمام حقوق این سایت متعلق به وبسایتcodebymeمیباشد.