Go Eşzamanlılık Başlangıç Paketi
Genel Bakış
Kısa Bir Giriş
Go dilinde eşzamanlılık yönetimi için birçok araç bulunmaktadır. Bu makalede, bu araçlardan bazılarını ve bazı püf noktalarını tanıtacağız.
Goroutine Nedir?
Goroutine, Go dilinin desteklediği yeni bir eşzamanlılık modelidir. Genellikle programlar, aynı anda birden çok işi gerçekleştirmek için işletim sisteminden (OS) OS thread'leri alır ve çekirdek sayısı kadar paralel olarak çalışır. Daha küçük eşzamanlılık birimlerini gerçekleştirmek için ise kullanıcı alanında green thread'ler oluşturulur ve bir OS thread'i içinde birden çok green thread'in çalışarak görevleri yapması sağlanır. Ancak goroutine'ler bu tür green thread'leri daha küçük ve verimli hale getirmiştir. Bu goroutine'ler, thread'lerden daha az bellek kullanır ve thread'lerden daha hızlı oluşturulup değiştirilebilirler.
Goroutine'leri kullanmak için yalnızca go
anahtar kelimesini kullanmak yeterlidir. Bu, program yazma sürecinde eşzamanlı (senkron) kodu sezgisel olarak eşzamansız (asenkron) kod olarak çalıştırmayı sağlar.
1package main
2
3import (
4 "fmt"
5 "time"
6)
7
8func main() {
9 ch := make(chan struct{})
10 go func() {
11 defer close(ch)
12 time.Sleep(1 * time.Second)
13 fmt.Println("Hello, World!")
14 }()
15
16 fmt.Println("Waiting for goroutine...")
17 for range ch {}
18}
Bu kod, basitçe 1 saniye bekleyip Hello, World!
çıktısını veren senkron bir kodu asenkron bir akışa dönüştürür. Şimdiki örnek basit olsa da, biraz daha karmaşık bir kodu senkron koddan asenkron koda dönüştürdüğümüzde, kodun okunabilirliği, görünürlüğü ve anlaşılırlığı mevcut async await veya promise gibi yöntemlerden daha iyi olur.
Ancak çoğu durumda, bu tür senkron kodları basitçe asenkron olarak çağırmanın akışını ve fork & join
gibi akışları (böl ve fethet'e benzer bir akış) anlamadan kötü goroutine kodları yazılabilir. Bu gibi durumlara karşı önlem alabilecek bazı yöntemleri ve teknikleri bu makalede tanıtacağız.
Eşzamanlılık Yönetimi
Context
İlk yönetim tekniği olarak context
'in ortaya çıkması şaşırtıcı olabilir. Ancak Go dilinde context
, basit bir iptal özelliğinden daha öte, tüm görev ağacını yönetmede mükemmel bir rol oynar. Eğer bilmeyenler varsa, bu paketi kısaca açıklayalım.
1package main
2
3func main() {
4 ctx, cancel := context.WithCancel(context.Background())
5 defer cancel()
6
7 go func() {
8 <-ctx.Done()
9 fmt.Println("Context is done!")
10 }()
11
12 time.Sleep(1 * time.Second)
13
14 cancel()
15
16 time.Sleep(1 * time.Second)
17}
Yukarıdaki kod, context
'i kullanarak 1 saniye sonra Context is done!
çıktısını veren bir koddur. context
, Done()
metodu aracılığıyla iptal edilip edilmediğini kontrol edebilir ve WithCancel
, WithTimeout
, WithDeadline
, WithValue
gibi metotlarla çeşitli iptal yöntemleri sunar.
Basit bir örnek oluşturalım. Diyelim ki bazı verileri almak için aggregator
kalıbını kullanarak user
, post
ve comment
verilerini getiren bir kod yazıyorsunuz. Ve tüm isteklerin 2 saniye içinde yapılması gerekiyorsa, aşağıdaki gibi yazabilirsiniz:
1package main
2
3func main() {
4 ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Second)
5 defer cancel()
6
7 ch := make(chan struct{})
8 go func() {
9 defer close(ch)
10 user := getUser(ctx)
11 post := getPost(ctx)
12 comment := getComment(ctx)
13
14 fmt.Println(user, post, comment)
15 }()
16
17 select {
18 case <-ctx.Done():
19 fmt.Println("Timeout!")
20 case <-ch:
21 fmt.Println("All data is fetched!")
22 }
23}
Yukarıdaki kod, 2 saniye içinde tüm verileri alamazsa Timeout!
çıktısını verir ve tüm verileri alırsa All data is fetched!
çıktısını verir. Bu şekilde context
kullanılarak, birden çok goroutine'in çalıştığı kodlarda bile iptal ve zaman aşımı kolayca yönetilebilir.
Bununla ilgili çeşitli context fonksiyonları ve metotları godoc context adresinde görülebilir. Basit olanları öğrenerek rahatça kullanabilirsiniz.
Channel
Unbuffered Channel
channel
, goroutine'ler arasında iletişim için kullanılan bir araçtır. channel
, make(chan T)
ile oluşturulabilir. Burada T
, ilgili channel
'ın ileteceği veri türüdür. channel
'dan <-
ile veri gönderilip alınabilir ve close
ile channel
kapatılabilir.
1package main
2
3func main() {
4 ch := make(chan int)
5 go func() {
6 ch <- 1
7 ch <- 2
8 close(ch)
9 }()
10
11 for i := range ch {
12 fmt.Println(i)
13 }
14}
Yukarıdaki kod, channel
kullanarak 1 ve 2'yi yazdıran bir koddur. Bu kodda, basitçe channel
'a değer gönderilip alınması gösterilmektedir. Ancak channel
bundan daha fazla işlevsellik sunar. Öncelikle buffered channel
ve unbuffered channel
hakkında bilgi edinelim. Başlamadan önce yukarıda yazılan örnek bir unbuffered channel
'dır ve kanala veri gönderme ve veri alma davranışları aynı anda gerçekleşmelidir. Bu davranışlar aynı anda gerçekleşmezse, deadlock meydana gelebilir.
Buffered Channel
Yukarıdaki kodun basit çıktı almak yerine ağır işler yapan 2 süreç olduğunu düşünün. İkinci süreç okuyup işlem yaparken uzun süre takılırsa, ilk süreç de o süre boyunca duracaktır. Bu durumu önlemek için buffered channel
kullanabiliriz.
1package main
2
3func main() {
4 ch := make(chan int, 2)
5 go func() {
6 ch <- 1
7 ch <- 2
8 close(ch)
9 }()
10
11 for i := range ch {
12 fmt.Println(i)
13 }
14}
Yukarıdaki kod, buffered channel
kullanarak 1 ve 2'yi yazdıran bir koddur. Bu kodda, buffered channel
kullanılarak channel
'a veri gönderme ve veri alma davranışlarının aynı anda gerçekleşmesi gerekmeyecek şekilde yapılmıştır. Böylece kanala bir tampon eklenirse, bu tampon uzunluğu kadar bir pay oluşur ve sonraki görevlerin etkisinden dolayı oluşan iş gecikmesini önleyebiliriz.
Select
Birden çok channel ile çalışırken, select
ifadesini kullanarak kolayca fan-in
yapısı oluşturulabilir.
1package main
2
3import (
4 "fmt"
5 "time"
6)
7
8func main() {
9 ch1 := make(chan int, 10)
10 ch2 := make(chan int, 10)
11 ch3 := make(chan int, 10)
12
13 go func() {
14 for {
15 ch1 <- 1
16 time.Sleep(1 * time.Second)
17 }
18 }()
19 go func() {
20 for {
21 ch2 <- 2
22 time.Sleep(2 * time.Second)
23 }
24 }()
25 go func() {
26 for {
27 ch3 <- 3
28 time.Sleep(3 * time.Second)
29 }
30 }()
31
32 for i := 0; i < 3; i++ {
33 select {
34 case v := <-ch1:
35 fmt.Println(v)
36 case v := <-ch2:
37 fmt.Println(v)
38 case v := <-ch3:
39 fmt.Println(v)
40 }
41 }
42}
Yukarıdaki kod, periyodik olarak 1, 2, 3 değerlerini ileten 3 adet channel oluşturur ve select
kullanarak channel'lardan değer alıp yazdıran bir koddur. Bu şekilde select
kullanılarak, birden çok channel'dan aynı anda veri alınabilirken, channel'dan değerler alındığı şekilde işlenebilir.
For Range
channel
'dan for range
kullanarak kolayca veri alınabilir. for range
bir channel üzerinde kullanıldığında, o channel'a veri eklendikçe çalışır ve channel kapatıldığında döngüyü sonlandırır.
1package main
2
3func main() {
4 ch := make(chan int)
5 go func() {
6 ch <- 1
7 ch <- 2
8 close(ch)
9 }()
10
11 for i := range ch {
12 fmt.Println(i)
13 }
14}
Yukarıdaki kod, channel
kullanarak 1 ve 2'yi yazdıran bir koddur. Bu kodda, for range
kullanılarak channel'a veri eklendikçe veri alınıp yazdırılır. Ve channel kapatıldığında döngü sonlanır.
Yukarıda birkaç kez yazdığımız gibi, bu sözdizimi basit bir senkronizasyon aracı olarak da kullanılabilir.
1package main
2
3func main() {
4 ch := make(chan struct{})
5 go func() {
6 defer close(ch)
7 time.Sleep(1 * time.Second)
8 fmt.Println("Hello, World!")
9 }()
10
11 fmt.Println("Waiting for goroutine...")
12 for range ch {}
13}
Yukarıdaki kod, 1 saniye bekleyip Hello, World!
çıktısını veren bir koddur. Bu kodda, channel
kullanılarak senkron bir kod asenkron bir koda dönüştürülmüştür. Bu şekilde channel
kullanılarak, senkron kodlar kolayca asenkron kodlara dönüştürülebilir ve join
noktaları ayarlanabilir.
Diğer
nil
bir channel'a veri gönderilirse veya alınırsa, sonsuz bir döngüye girilerek deadlock oluşabilir.- Channel kapatıldıktan sonra veri gönderilirse, panic meydana gelir.
- Channel'lar kapatılmasa bile GC tarafından toplanır ve kapatılır.
Mutex
Spinlock
spinlock
, bir döngü içinde sürekli olarak kilidi almaya çalışan bir senkronizasyon yöntemidir. Go dilinde pointer'lar kullanılarak kolayca bir spinlock uygulanabilir.
1package spinlock
2
3import (
4 "runtime"
5 "sync/atomic"
6)
7
8type SpinLock struct {
9 lock uintptr
10}
11
12func (s *SpinLock) Lock() {
13 for !atomic.CompareAndSwapUintptr(&s.lock, 0, 1) {
14 runtime.Gosched()
15 }
16}
17
18func (s *SpinLock) Unlock() {
19 atomic.StoreUintptr(&s.lock, 0)
20}
21
22func NewSpinLock() *SpinLock {
23 return &SpinLock{}
24}
Yukarıdaki kod, spinlock
paketini uygulayan bir koddur. Bu kodda, sync/atomic
paketi kullanılarak SpinLock
uygulanmıştır. Lock
metodu, atomic.CompareAndSwapUintptr
kullanarak kilidi almaya çalışır ve Unlock
metodu, atomic.StoreUintptr
kullanarak kilidi serbest bırakır. Bu yöntem, sürekli olarak kilidi almaya çalıştığı için, kilidi alana kadar sürekli CPU kullanır ve sonsuz bir döngüye girebilir. Bu nedenle, spinlock
basit senkronizasyonlarda veya kısa süreler için kullanılması önerilir.
sync.Mutex
mutex
, goroutine'ler arasındaki senkronizasyon için kullanılan bir araçtır. sync
paketi tarafından sağlanan mutex
, Lock
, Unlock
, RLock
, RUnlock
gibi metotlar sunar. mutex
, sync.Mutex
ile oluşturulabilir ve sync.RWMutex
ile okuma/yazma kilitleri de kullanılabilir.
1package main
2
3import (
4 "sync"
5)
6
7func main() {
8 var mu sync.Mutex
9 var count int
10
11 go func() {
12 mu.Lock()
13 count++
14 mu.Unlock()
15 }()
16
17 mu.Lock()
18 count++
19 mu.Unlock()
20
21 println(count)
22}
Yukarıdaki kodda, neredeyse aynı anda iki goroutine aynı count
değişkenine erişecektir. Bu durumda, mutex
kullanılarak count
değişkenine erişen kod kritik bir bölge haline getirilirse, count
değişkenine eşzamanlı erişim engellenebilir. Böylece bu kod kaç kez çalıştırılırsa çalıştırılsın aynı şekilde 2
çıktısını verecektir.
sync.RWMutex
sync.RWMutex
, okuma ve yazma kilitlerini ayrı ayrı kullanabilen bir mutex
türüdür. RLock
ve RUnlock
metotları kullanılarak okuma kilidi alınabilir ve bırakılabilir.
1package cmap
2
3import (
4 "sync"
5)
6
7type ConcurrentMap[K comparable, V any] struct {
8 sync.RWMutex
9 data map[K]V
10}
11
12func (m *ConcurrentMap[K, V]) Get(key K) (V, bool) {
13 m.RLock()
14 defer m.RUnlock()
15
16 value, ok := m.data[key]
17 return value, ok
18}
19
20func (m *ConcurrentMap[K, V]) Set(key K, value V) {
21 m.Lock()
22 defer m.Unlock()
23
24 m.data[key] = value
25}
Yukarıdaki kod, sync.RWMutex
kullanılarak ConcurrentMap
'i uygulayan bir koddur. Bu kodda, Get
metodu okuma kilidi alırken, Set
metodu yazma kilidi alarak data
map'ine güvenli bir şekilde erişip değişiklik yapılabilir. Okuma kilidine ihtiyaç duyulmasının sebebi, basit okuma işlemlerinin fazla olduğu durumlarda, yazma kilidi yerine sadece okuma kilidi alarak birden çok goroutine'in aynı anda okuma işlemini gerçekleştirmesini sağlamaktır. Böylece, durum değişikliği gerektirmeyen ve yazma kilidi alınması gerekmeyen durumlarda, sadece okuma kilidi alarak performansı artırabiliriz.
fakelock
fakelock
, sync.Locker
'ı uygulayan basit bir püf noktasıdır. Bu yapı, sync.Mutex
ile aynı metotları sunar, ancak gerçekte herhangi bir işlem yapmaz.
1package fakelock
2
3type FakeLock struct{}
4
5func (f *FakeLock) Lock() {}
6
7func (f *FakeLock) Unlock() {}
Yukarıdaki kod, fakelock
paketini uygulayan bir koddur. Bu paket, sync.Locker
'ı uygulayarak Lock
ve Unlock
metotlarını sunar, ancak gerçekte hiçbir işlem yapmaz. Neden böyle bir koda ihtiyaç duyulduğu fırsat olduğunda anlatılacaktır.
Waitgroup
sync.WaitGroup
sync.WaitGroup
, goroutine'lerin tamamının işi bitene kadar beklemek için kullanılan bir araçtır. Add
, Done
, Wait
metotları sunar; Add
metodu ile goroutine sayısı artırılır, Done
metodu ile goroutine'in işinin bittiği bildirilir ve Wait
metodu ile tüm goroutine'lerin işi bitene kadar beklenir.
1package main
2
3import (
4 "sync"
5 "sync/atomic"
6)
7
8func main() {
9 wg := sync.WaitGroup{}
10 c := atomic.Int64{}
11
12 for i := 0; i < 100 ; i++ {
13 wg.Add(1)
14 go func() {
15 defer wg.Done()
16 c.Add(1)
17 }()
18 }
19
20 wg.Wait()
21 println(c.Load())
22}
Yukarıdaki kod, sync.WaitGroup
kullanarak 100 adet gorutinin eş zamanlı olarak c
değişkenine değer eklediği bir koddur. Bu kodda, sync.WaitGroup
kullanılarak tüm gorutinlerin bitmesini bekledikten sonra c
değişkenine eklenen değer yazdırılır. Basitçe birkaç işlemi fork & join
yapmak için sadece kanallar yeterli olsa da, çok sayıda işlemi fork & join
yaparken sync.WaitGroup
kullanmak da iyi bir seçenektir.
slice ile
Eğer slice ile birlikte kullanılırsa, waitgroup
, kilitler olmadan eş zamanlı olarak işleri yönetmek için harika bir araç olabilir.
1package main
2
3import (
4 "fmt"
5 "sync"
6 "rand"
7)
8
9func main() {
10 var wg sync.WaitGroup
11 arr := [10]int{}
12
13 for i := 0; i < 10; i++ {
14 wg.Add(1)
15 go func(id int) {
16 defer wg.Done()
17
18 arr[id] = rand.Intn(100)
19 }(i)
20 }
21
22 wg.Wait()
23 fmt.Println("Done")
24
25 for i, v := range arr {
26 fmt.Printf("arr[%d] = %d\n", i, v)
27 }
28}
Yukarıdaki kod, sadece waitgroup
kullanarak her bir gorutinin aynı anda 10 adet rastgele tamsayı ürettiği ve aldığı indekse kaydettiği bir koddur. Bu kodda, waitgroup
kullanılarak tüm gorutinlerin bitmesini bekledikten sonra Done
çıktısı verilir. Bu şekilde waitgroup
kullanılırsa, birçok gorutin aynı anda iş yapabilir, tüm gorutinler bitene kadar kilitler olmadan veri kaydedilebilir ve iş bittikten sonra toplu olarak son işlem yapılabilir.
golang.org/x/sync/errgroup.ErrGroup
errgroup
, sync.WaitGroup
'in genişletilmiş bir paketidir. errgroup
, sync.WaitGroup
'ten farklı olarak, gorutinlerin işlerinden herhangi birinde hata oluşursa tüm gorutinleri iptal eder ve hatayı döndürür.
1package main
2
3import (
4 "context"
5 "fmt"
6 "golang.org/x/sync/errgroup"
7)
8
9func main() {
10 g, ctx := errgroup.WithContext(context.Background())
11 _ = ctx
12
13 for i := 0; i < 10; i++ {
14 i := i
15 g.Go(func() error {
16 if i == 5 {
17 return fmt.Errorf("error")
18 }
19 return nil
20 })
21 }
22
23 if err := g.Wait(); err != nil {
24 fmt.Println(err)
25 }
26}
Yukarıdaki kod, errgroup
kullanarak 10 adet gorutin oluşturup 5. gorutinde hata oluşturan bir koddur. Kasıtlı olarak 5. gorutinde hata oluşturarak, hata oluştuğu durumları göstermiş olduk. Ancak, gerçek kullanımda errgroup
kullanılarak gorutinler oluşturulur ve her bir gorutinde hata oluştuğu durumlarda çeşitli son işlemler yapılır.
once
Sadece bir kez çalışması gereken kodları çalıştırmak için kullanılan bir araçtır. Aşağıdaki oluşturucular aracılığıyla ilgili kod çalıştırılabilir.
1func OnceFunc(f func()) func()
2func OnceValue[T any](f func() T) func() T
3func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2)
OnceFunc
OnceFunc
sadece ilgili fonksiyonun tüm süreç boyunca yalnızca bir kere çalışmasını sağlar.
1package main
2
3import "sync"
4
5func main() {
6 once := sync.OnceFunc(func() {
7 println("Hello, World!")
8 })
9
10 once()
11 once()
12 once()
13 once()
14 once()
15}
Yukarıdaki kod, sync.OnceFunc
kullanarak Hello, World!
yazdıran bir koddur. Bu kodda, sync.OnceFunc
kullanılarak once
fonksiyonu oluşturulur ve once
fonksiyonu birden çok kez çağrılsa bile Hello, World!
sadece bir kez yazdırılır.
OnceValue
OnceValue
sadece ilgili fonksiyonun tüm süreç boyunca yalnızca bir kere çalışmasını sağlamakla kalmaz, aynı zamanda ilgili fonksiyonun geri dönüş değerini de kaydederek tekrar çağrıldığında kaydedilmiş değeri döndürür.
1package main
2
3import "sync"
4
5func main() {
6 c := 0
7 once := sync.OnceValue(func() int {
8 c += 1
9 return c
10 })
11
12 println(once())
13 println(once())
14 println(once())
15 println(once())
16 println(once())
17}
Yukarıdaki kod, sync.OnceValue
kullanarak c
değişkenini 1 artırıp değerini döndüren bir koddur. Bu kodda, sync.OnceValue
kullanılarak once
fonksiyonu oluşturulur ve once
fonksiyonu birden çok kez çağrılsa bile c
değişkeni sadece bir kez artırılmış 1 değerini döndürür.
OnceValues
OnceValues
, OnceValue
ile aynı şekilde çalışır ancak birden fazla değer döndürebilir.
1package main
2
3import "sync"
4
5func main() {
6 c := 0
7 once := sync.OnceValues(func() (int, int) {
8 c += 1
9 return c, c
10 })
11
12 a, b := once()
13 println(a, b)
14 a, b = once()
15 println(a, b)
16 a, b = once()
17 println(a, b)
18 a, b = once()
19 println(a, b)
20 a, b = once()
21 println(a, b)
22}
Yukarıdaki kod, sync.OnceValues
kullanarak c
değişkenini 1 artırıp değerini döndüren bir koddur. Bu kodda, sync.OnceValues
kullanılarak once
fonksiyonu oluşturulur ve once
fonksiyonu birden çok kez çağrılsa bile c
değişkeni sadece bir kez artırılmış 1 değerini döndürür.
atomic
atomic
paketi, atomik işlemleri sağlayan bir pakettir. atomic
paketi Add
, CompareAndSwap
, Load
, Store
, Swap
gibi metotlar sunar ancak, son zamanlarda Int64
, Uint64
, Pointer
gibi tiplerin kullanımı önerilmektedir.
1package main
2
3import (
4 "sync"
5 "sync/atomic"
6)
7
8func main() {
9 wg := sync.WaitGroup{}
10 c := atomic.Int64{}
11
12 for i := 0; i < 100 ; i++ {
13 wg.Add(1)
14 go func() {
15 defer wg.Done()
16 c.Add(1)
17 }()
18 }
19
20 wg.Wait()
21 println(c.Load())
22}
Daha önce kullanılan bir örnektir. atomic.Int64
tipini kullanarak c
değişkenini atomik olarak artıran bir koddur. Add
metodu ve Load
metodu ile atomik olarak değişken artırılabilir ve değişken okunabilir. Ayrıca Store
metodu ile değer kaydedilebilir, Swap
metodu ile değer değiştirilebilir ve CompareAndSwap
metodu ile değer karşılaştırıldıktan sonra uygunsa değiştirilebilir.
cond
sync.Cond
cond
paketi, koşul değişkenleri sağlayan bir pakettir. cond
paketi sync.Cond
ile oluşturulabilir ve Wait
, Signal
, Broadcast
metotları sunar.
1package main
2
3import (
4 "sync"
5)
6
7func main() {
8 c := sync.NewCond(&sync.Mutex{})
9 ready := false
10
11 go func() {
12 c.L.Lock()
13 ready = true
14 c.Signal()
15 c.L.Unlock()
16 }()
17
18 c.L.Lock()
19 for !ready {
20 c.Wait()
21 }
22 c.L.Unlock()
23
24 println("Ready!")
25}
Yukarıdaki kod, sync.Cond
kullanılarak ready
değişkeni true
olana kadar bekleyen bir koddur. Bu kodda, sync.Cond
kullanılarak ready
değişkeni true
olana kadar beklenir ve sonra Ready!
yazdırılır. Bu şekilde sync.Cond
kullanılırsa, birçok gorutin aynı anda belirli bir koşul sağlanana kadar bekleyebilir.
Bunu kullanarak basit bir queue
implementasyonu yapılabilir.
1package queue
2
3import (
4 "sync"
5 "sync/atomic"
6)
7
8type Node[T any] struct {
9 Value T
10 Next *Node[T]
11}
12
13type Queue[T any] struct {
14 sync.Mutex
15 Cond *sync.Cond
16 Head *Node[T]
17 Tail *Node[T]
18 Len int
19}
20
21func New[T any]() *Queue[T] {
22 q := &Queue[T]{}
23 q.Cond = sync.NewCond(&q.Mutex)
24 return q
25}
26
27func (q *Queue[T]) Push(value T) {
28 q.Lock()
29 defer q.Unlock()
30
31 node := &Node[T]{Value: value}
32 if q.Len == 0 {
33 q.Head = node
34 q.Tail = node
35 } else {
36 q.Tail.Next = node
37 q.Tail = node
38 }
39 q.Len++
40 q.Cond.Signal()
41}
42
43func (q *Queue[T]) Pop() T {
44 q.Lock()
45 defer q.Unlock()
46
47 for q.Len == 0 {
48 q.Cond.Wait()
49 }
50
51 node := q.Head
52 q.Head = q.Head.Next
53 q.Len--
54 return node.Value
55}
Bu şekilde sync.Cond
kullanılarak, spin-lock
ile çok fazla CPU kullanımı yapmak yerine verimli bir şekilde beklenir ve koşul sağlanırsa tekrar çalışılabilir.
semaphore
golang.org/x/sync/semaphore.Semaphore
semaphore
paketi, semaforlar sağlayan bir pakettir. semaphore
paketi golang.org/x/sync/semaphore.Semaphore
ile oluşturulabilir ve Acquire
, Release
, TryAcquire
metotları sunar.
1package main
2
3import (
4 "context"
5 "fmt"
6 "golang.org/x/sync/semaphore"
7)
8
9func main() {
10 s := semaphore.NewWeighted(1)
11
12 if s.TryAcquire(1) {
13 fmt.Println("Acquired!")
14 } else {
15 fmt.Println("Not Acquired!")
16 }
17
18 s.Release(1)
19}
Yukarıdaki kod, semaphore
kullanarak bir semafor oluşturup, semaforu kullanarak Acquire
metodu ile semaforu elde eden ve Release
metodu ile semaforu serbest bırakan bir koddur. Bu kodda, semaphore
kullanılarak semaforun nasıl elde edildiği ve serbest bırakıldığı gösterilmiştir.
Sonuç
Temel bilgiler buraya kadar yeterli olacaktır. Bu makalenin içeriğine dayanarak, gorutinleri kullanarak eş zamanlılığı yönetme yöntemlerini anlamış ve gerçekten kullanabilir hale gelmiş olmanız dileğiyle. Umarım bu makale size yardımcı olmuştur. Teşekkürler.