همزمانی یکی از نقاط قوت اصلی Go است که آن را برای ساخت سیستمهای مقیاسپذیر و کارآمد ایدهآل میکند. Go از goroutines (واحدهای سبک اجرای موازی) و channels (ابزارهای ارتباطی بین goroutineها) برای مدیریت همزمانی استفاده میکند. این جزوه تمام جنبههای همزمانی در Go را با جزئیات کامل پوشش میدهد.
Goroutine یک تابع است که به صورت موازی و مستقل از تابع اصلی اجرا میشود. برخلاف threadهای سیستمعامل که سنگین هستند، goroutineها سبکوزناند (چند کیلوبایت حافظه) و توسط runtime Go مدیریت میشوند.
برای ایجاد یک goroutine، کافی است کلمه کلیدی go
را قبل از فراخوانی تابع قرار دهید:
package main
import (
"fmt"
"time"
)
func sayHello() {
for i := 0; i < 3; i++ {
fmt.Println("Hello from goroutine")
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go sayHello() // اجرای موازی
for i := 0; i < 3; i++ {
fmt.Println("Hello from main")
time.Sleep(100 * time.Millisecond)
}
time.Sleep(1 * time.Second) // منتظر اتمام goroutine
}
sayHello
و main
به صورت موازی اجرا میشوند.sync.WaitGroup
یا channels
استفاده میشود (در بخشهای بعدی توضیح داده میشود).main
پایان یابد، تمام goroutineها متوقف میشوند.pprof
برای بررسی تعداد goroutineها استفاده کنید:
go tool pprof http://localhost:6060/debug/pprof/goroutine
time.Sleep
).کانالها (Channels) ابزار اصلی برای ارتباط و هماهنگی بین goroutineها هستند. آنها امکان ارسال و دریافت دادهها را به صورت ایمن فراهم میکنند.
ch := make(chan int)
ch := make(chan int, 5) // ظرفیت 5
ch <- value
value := <-ch
close(ch)
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "Hello from goroutine"
close(ch)
}()
msg := <-ch
fmt.Println(msg) // Hello from goroutine
}
value, ok := <-ch
if !ok {
fmt.Println("Channel closed")
}
func send(ch chan<- int) { // فقط ارسال
ch <- 42
}
ch := make(chan int)
ch <- 1 // Deadlock: هیچ دریافتکنندهای نیست
دستور select
برای مدیریت چندین کانال به صورت همزمان استفاده میشود. شبیه switch
است، اما برای عملیات کانالها.
select {
case v := <-ch1:
fmt.Println("Received from ch1:", v)
case ch2 <- x:
fmt.Println("Sent to ch2")
default:
fmt.Println("No operation")
}
select {
case v := <-ch:
fmt.Println(v)
case <-time.After(2 * time.Second):
fmt.Println("Timeout")
}
done := make(chan struct{})
go func() {
time.Sleep(1 * time.Second)
close(done)
}()
select {
case <-done:
fmt.Println("Done")
case <-ch:
fmt.Println("Received")
}
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "from ch1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "from ch2"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
}
default
وجود نداشته باشد.Go از الگوهای مختلفی برای حل مسائل همزمانی استفاده میکند. در ادامه، الگوهای مهم شرح داده میشوند.
Worker Pool مجموعهای از goroutineها (کارگران) است که وظایف را از یک کانال دریافت کرده و پردازش میکنند.
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, j)
results <- j * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
var wg sync.WaitGroup
// راهاندازی 3 کارگر
for w := 1; w <= 3; w++ {
wg.Add(1)
go func(wid int) {
defer wg.Done()
worker(wid, jobs, results)
}(w)
}
// ارسال کارها
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
// جمعآوری نتایج
go func() {
wg.Wait()
close(results)
}()
for r := range results {
fmt.Println("Result:", r)
}
}
runtime.NumCPU()
).package main
import (
"fmt"
"sync"
)
func producer(nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
out <- n
}
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
out <- n * n
}
}()
return out
}
func merge(cs ...<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
wg.Add(len(cs))
for _, c := range cs {
go func(ch <-chan int) {
defer wg.Done()
for n := range ch {
out <- n
}
}(c)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
func main() {
in := producer(1, 2, 3, 4)
c1 := square(in)
c2 := square(in) // Fan-out: دو goroutine برای محاسبه مربع
out := merge(c1, c2) // Fan-in: جمعآوری نتایج
for n := range out {
fmt.Println(n)
}
}
Pipeline مجموعهای از مراحل پردازش است که هر مرحله خروجی خود را به مرحله بعدی ارسال میکند.
package main
import "fmt"
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
func main() {
for n := range square(gen(2, 3, 4)) {
fmt.Println(n) // 4, 9, 16
}
}
Generator یک goroutine است که مقادیر را به صورت تدریجی تولید میکند.
package main
import (
"fmt"
"time"
)
func generator() <-chan int {
out := make(chan int)
go func() {
for i := 1; ; i++ {
out <- i
time.Sleep(500 * time.Millisecond)
}
}()
return out
}
func main() {
nums := generator()
for i := 0; i < 5; i++ {
fmt.Println(<-nums)
}
}
برای انتظار اتمام مجموعهای از goroutineها:
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
// کار
}()
wg.Wait()
برای جلوگیری از دسترسی همزمان به منابع مشترک:
var (
counter int
mu sync.Mutex
)
func increment() {
mu.Lock()
defer mu.Unlock()
counter++
}
برای خواندن/نوشتن همزمان:
var (
data map[string]int
rwmu sync.RWMutex
)
func read(key string) int {
rwmu.RLock()
defer rwmu.RUnlock()
return data[key]
}
برای عملیات اتمیک بدون قفل:
var counter int64
atomic.AddInt64(&counter, 1)
fmt.Println(atomic.LoadInt64(&counter)) // 1
atomic
استفاده کنید.defer
).پکیج context
برای مدیریت لغو، مهلت زمانی، و انتقال داده بین goroutineها استفاده میشود.
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx = context.WithValue(ctx, "userID", 123)
userID := ctx.Value("userID").(int)
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
go func() {
select {
case <-time.After(3 * time.Second):
fmt.Println("Work completed")
case <-ctx.Done():
fmt.Println("Work cancelled:", ctx.Err())
}
}()
time.Sleep(3 * time.Second)
}
cancel()
را فراخوانی کنید تا منابع آزاد شوند.go run -race main.go
sync.Mutex
یا sync.RWMutex
.atomic
برای عملیات ساده.fmt.Println(runtime.NumGoroutine())
go tool pprof http://localhost:6060/debug/pprof/profile
sync.Pool
برای اشیاء موقت استفاده کنید.این جزوه تمام جنبههای همزمانی در Go را با جزئیات کامل پوشش داد. از مفاهیم پایه مثل goroutineها و کانالها تا الگوهای پیشرفته مثل Worker Pool و Pipeline، و ابزارهای مدیریت منابع مثل Context و sync، هر بخش با مثالهای عملی و نکات پیشرفته ارائه شد. برای یادگیری عمیقتر:
https://golang.org/doc/