GoSuda

Go Eşzamanlılık Başlangıç Paketi

By snowmerak
views ...

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

  1. nil bir channel'a veri gönderilirse veya alınırsa, sonsuz bir döngüye girilerek deadlock oluşabilir.
  2. Channel kapatıldıktan sonra veri gönderilirse, panic meydana gelir.
  3. 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.