GoSuda

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

By snowmerak views ...

Genel Bakış

Kısa Giriş

Go dilinde eşzamanlılığı yönetmek için birçok araç bulunmaktadır. Bu makalede bunlardan bazılarını ve püf noktalarını tanıtmak istiyorum.

Goroutine?

goroutine, Go dilinin desteklediği yeni bir eşzamanlılık modelidir. Genel olarak bir program, aynı anda birden fazla işi yürütebilmek için işletim sisteminden OS iş parçacıkları (thread) alır ve bunları çekirdek sayısı kadar paralel olarak çalıştırır. Daha küçük ölçekte eşzamanlılık gerçekleştirmek için ise kullanıcı alanında green thread'ler oluşturulur ve tek bir OS iş parçacığı içinde birden fazla green thread sırayla çalışarak görevleri yürütür. Ancak goroutine'lerde bu biçimdeki green thread'ler daha küçük ve daha verimli hale getirilmiştir. Bu goroutine'ler iş parçacıklarından daha az bellek kullanır ve iş parçacıklarından daha hızlı oluşturulabilir ve değiştirilebilir.

Goroutine kullanmak için yalnızca go anahtar sözcüğünü kullanmak yeterlidir. Bu, program yazma sürecinde senkron kodu sezgisel olarak asenkron biçimde çalıştırmaya olanak tanır.

 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, 1 saniye bekleyip Hello, World! yazdıran senkron kodu asenkron bir akışa dönüştürmektedir. Mevcut örnek basit olmakla birlikte, daha karmaşık bir kodu senkrondan asenkrona dönüştürdüğünüzde kodun okunabilirliği, görünürlüğü ve anlaşılırlığı, async await veya promise gibi mevcut yöntemlere kıyasla daha iyi hale gelir.

Ancak birçok durumda, bu tür senkron kodu yalnızca asenkron olarak çağırma akışını ve fork & join gibi bir akışı (bölle ve yönet yaklaşımına benzer bir akış) anlamadan kullanıldığında, kötü goroutine kodları ortaya çıkabilmektedir. Bu durumlara karşı alınabilecek birkaç yöntem ve teknik bu makalede tanıtılacaktır.

Eşzamanlılık Yönetimi

context

İlk yönetim tekniğinin context olması sizi şaşırtabilir. Ancak Go dilinde context, basit iptal işlevinin ötesinde, tüm iş ağacını yönetmede öne çıkan bir rol üstlenir. Bilmeyenler için bu paketi kısaca açıklayayı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 kullanarak 1 saniye sonra Context is done! yazdıran bir koddur. context, Done() metodu aracılığıyla iptal durumunu kontrol etmenize olanak tanır ve WithCancel, WithTimeout, WithDeadline, WithValue gibi metodlarla çeşitli iptal yöntemleri sunar.

Basit bir örnek oluşturalım. Diyelim ki bir veriyi almak için aggregator desenini kullanarak user, post ve comment verilerini getiren bir kod yazıyorsunuz ve tüm isteklerin 2 saniye içinde gerçekleşmesi gerekiyor. Bu durumda 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! yazdırır, tüm verileri alırsa All data is fetched! yazdırır. Bu yöntemle context kullanıldığında, birden fazla goroutine'in çalıştığı kodda bile iptal ve zaman aşımı işlemleri kolayca yönetilebilir.

Context ile ilgili çeşitli fonksiyon ve metodlara godoc context adresinden ulaşabilirsiniz. Temel olanları öğrenip rahatça kullanabilmeniz dileğiyle.

channel

unbuffered channel

channel, goroutine'ler arasında iletişim kurmak için kullanılan bir araçtır. channel, make(chan T) ile oluşturulabilir. Burada T, ilgili channel'ın ileteceği verinin tipidir. channel ile <- kullanılarak 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 yazdıran bir koddur. Bu kodda yalnızca channel'a değer gönderme ve alma işlemi gösterilmektedir. Ancak channel bundan daha fazla işlev sunar. Öncelikle buffered channel ve unbuffered channel kavramlarını inceleyelim. Başlamadan önce, yukarıda yazılan örnek bir unbuffered channel örneğidir; channel'a veri gönderme eylemi ile veriyi alma eylemi aynı anda gerçekleşmelidir. Eğer bu eylemler aynı anda gerçekleşmezse deadlock oluşabilir.

buffered channel

Peki yukarıdaki kod basit bir çıktı yerine ağır işler yapan iki süreç olsaydı ne olurdu? İkinci süreç veriyi okuyup işlerken uzun süre takılırsa, birinci süreç de bu süre boyunca duracaktır. Bu durumu önlemek için buffered channel kullanabilirsiniz.

 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 yazdıran bir koddur. Bu kodda buffered channel kullanılarak, channel'a veri gönderme eyleminin ve veriyi alma eyleminin aynı anda gerçekleşmesi gerekmeyecek şekilde yapılandırılmıştır. Channel'a tampon eklendiğinde, bu uzunluk kadar boşluk oluşur ve sonraki görevlerin neden olduğu gecikmeler önlenebilir.

select

Birden fazla kanalla çalışırken, select söz dizimi kullanılarak fan-in yapısı kolayca uygulanabilir.

 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 ve 3 değerlerini ileten 3 kanal oluşturur ve select kullanarak kanallardan değer alıp yazdırır. Bu yöntemle select kullanıldığında, birden fazla kanaldan aynı anda veri alınabilir ve değerler alındıkları sırada işlenebilir.

for range

channel, for range kullanılarak kolayca veri alınabilir. for range bir kanal üzerinde kullanıldığında, kanala her veri eklendiğinde çalışır ve kanal 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 yazdıran bir koddur. Bu kodda for range kullanılarak kanala her veri eklendiğinde veri alınıp yazdırılır. Kanal kapatıldığında ise döngü sonlandırılır.

Yukarıda birkaç kez yazıldığı gibi, bu söz dizimi 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! yazdıran bir koddur. Bu kodda channel kullanılarak senkron kod asenkron koda dönüştürülmüştür. Bu yöntemle channel kullanıldığında, senkron kod kolayca asenkron koda dönüştürülebilir ve join noktası belirlenebilir.

etc

  1. Nil bir channel'a veri gönderilir veya ondan veri alınırsa, sonsuz döngüye girilip deadlock oluşabilir.
  2. Bir kanal kapatıldıktan sonra ona veri gönderilirse panic oluşur.
  3. Kanalı kapatmasanız bile, GC onu topladığında kanal kapatılmış olur.

mutex

spinlock

spinlock, bir döngü içinde sürekli olarak kilidi denemeye devam eden bir senkronizasyon yöntemidir. Go dilinde işaretçi kullanılarak spinlock kolayca 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 koddur. Bu kodda SpinLock, sync/atomic paketi kullanılarak uygulanmıştır. Lock metodunda kilidi denemek için atomic.CompareAndSwapUintptr kullanılır, Unlock metodunda ise kilidi serbest bırakmak için atomic.StoreUintptr kullanılır. Bu yöntem durmaksızın kilidi denediği için, kilit alınana kadar sürekli CPU kullanır ve sonsuz döngüye girebilir. Bu nedenle spinlock, basit senkronizasyonlarda veya yalnızca kısa süreli kullanımlarda tercih edilmelidir.

sync.Mutex

mutex, goroutine'ler arasında senkronizasyon sağlamak için kullanılan bir araçtır. sync paketinin sunduğu mutex, Lock, Unlock, RLock, RUnlock gibi metodlar sağlar. mutex, sync.Mutex ile oluşturulabilir ve okuma/yazma kilidi için sync.RWMutex 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şmektedir. Bu durumda, count değişkenine erişen kodu mutex kullanarak kritik bölge haline getirirseniz, count değişkenine eşzamanlı erişim engellenmiş olur. Bu durumda kod kaç kez çalıştırılırsa çalıştırılsın her zaman 2 yazdırır.

sync.RWMutex

sync.RWMutex, okuma kilidi ile yazma kilidini ayırarak kullanabileceğiniz bir mutextir. RLock ve RUnlock metodlarıyla okuma kilidi alınıp serbest 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 uygulanmış bir koddur. Bu kodda Get metodunda okuma kilidi, Set metodunda yazma kilidi kullanılarak data haritasına güvenli biçimde erişilip değiştirilebilir. Okuma kilidinin gerekliliği, yoğun biçimde yalnızca okuma işlemlerinin yapıldığı durumlarda, yazma kilidi kullanmadan yalnızca okuma kilidi kullanarak birden fazla goroutine'in aynı anda okuma işlemi yapabilmesini sağlamaktır. Böylece durumda bir değişiklik olmadığı için yazma kilidi gerekmeyen durumlarda yalnızca okuma kilidi kullanılarak performans artırılabilir.

fakelock

fakelock, sync.Locker arayüzünü uygulayan basit bir numaradır. Bu yapı, sync.Mutex ile aynı metodları sağlar 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 koddur. Bu paket, sync.Locker arayüzünü uygulayarak Lock ve Unlock metodlarını sağlar ancak gerçekte hiçbir işlem yapmaz. Bu tür bir kodun neden gerekli olduğunu fırsat olduğunda anlatacağım.

waitgroup

sync.WaitGroup

sync.WaitGroup, goroutine'lerin işlerinin tamamı bitene kadar beklemeye yarayan bir araçtır. Add, Done ve Wait metodlarını sağlar; Add metoduyla goroutine sayısı artırılır, Done metoduyla bir goroutine'in işinin bittiği bildirilir. Wait metoduyla ise tüm goroutine'lerin işlerinin bitmesi 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 kullanılarak 100 goroutine'in aynı anda c değişkenine değer eklediği bir koddur. Bu kodda sync.WaitGroup ile tüm goroutine'ler bitene kadar beklenir ve ardından c değişkenine eklenen değer yazdırılır. Birkaç işi basitçe fork & join ettiğiniz durumlarda yalnızca kanal kullanmak yeterlidir; ancak çok sayıda işi fork & join ettiğinizde sync.WaitGroup kullanmak da iyi bir seçenektir.

with slice

Bir dilim (slice) ile birlikte kullanıldığında, waitgroup kilit kullanmadan etkili bir eşzamanlı çalıştırma yönetim aracı 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, yalnızca waitgroup kullanarak her goroutine'in aynı anda 10 rastgele tam sayı üretip atanan indekse kaydettiği bir koddur. Bu kodda waitgroup ile tüm goroutine'ler bitene kadar beklenir ve ardından Done yazdırılır. Bu yöntemle waitgroup kullanıldığında, birden fazla goroutine aynı anda çalışabilir, tüm goroutine'ler bitene kadar veriler kilit kullanılmadan kaydedilebilir ve iş bittikten sonra toplu bir son işlem yapılabilir.

golang.org/x/sync/errgroup.ErrGroup

errgroup, sync.WaitGroup'un genişletilmiş bir paketidir. errgroup, sync.WaitGroup'tan farklı olarak, goroutine'lerin işlerinden herhangi biri hata verirse tüm goroutine'leri 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 goroutine oluşturan ve 5. goroutine'de hata üreten bir koddur. Hatanın oluşabileceği durumu göstermek amacıyla bilerek beşinci goroutine'de hata üretilmiştir. Gerçek kullanımda ise errgroup ile goroutine'ler oluşturulur ve her goroutine'de hata oluştuğunda çeşitli son işlemler uygulanarak kullanılır.

once

Yalnızca bir kez çalıştırılması gereken kodu çalıştıran bir araçtır. Aşağıdaki kurucu fonksiyonlar 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, ilgili fonksiyonun tüm program boyunca yalnızca bir kez çalıştırılabilmesini 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 kullanılarak Hello, World! yazdıran bir koddur. Bu kodda sync.OnceFunc ile once fonksiyonu oluşturulur ve once fonksiyonu birçok kez çağrılsa bile Hello, World! yalnızca bir kez yazdırılır.

OnceValue

OnceValue, ilgili fonksiyonun yalnızca bir kez çalışmasının yanı sıra, fonksiyonun dönüş değerini saklar ve tekrar çağrıldığında saklanan 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 kullanılarak c değişkenini 1 artıran bir koddur. Bu kodda sync.OnceValue ile once fonksiyonu oluşturulur ve once fonksiyonu birçok kez çağrılsa bile c değişkeni yalnızca bir kez artırılarak 1 döndürülü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 kullanılarak c değişkenini 1 artıran bir koddur. Bu kodda sync.OnceValues ile once fonksiyonu oluşturulur ve once fonksiyonu birçok kez çağrılsa bile c değişkeni yalnızca bir kez artırılarak 1 döndürülür.

atomic

atomic paketi, atomik işlemler sağlayan bir pakettir. atomic paketi Add, CompareAndSwap, Load, Store, Swap gibi metodlar sunar, ancak son zamanlarda Int64, Uint64, Pointer gibi tiplerin kullanılması ö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}

Bu, daha önce kullanılan bir örnektir. atomic.Int64 tipini kullanarak c değişkenini atomik olarak artıran bir koddur. Add metodu ve Load metoduyla değişken atomik olarak artırılabilir ve okunabilir. Ayrıca Store metoduyla değer kaydedilebilir, Swap metoduyla değer değiştirilebilir ve CompareAndSwap metoduyla değer karşılaştırılıp 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 metodlarını sağlar.

 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, ready değişkeni true olana kadar sync.Cond kullanılarak bekleyen bir koddur. Bu kodda sync.Cond ile ready değişkeni true olana kadar beklenir ve ardından Ready! yazdırılır. Bu yöntemle sync.Cond kullanıldığında, birden fazla goroutine belirli bir koşul sağlanana kadar bekletilebilir.

Bunu kullanarak basit bir queue uygulayabilirsiniz.

 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ıldığında, spin-lock ile yüksek CPU kullanımına yol açmak yerine verimli biçimde beklenebilir ve koşul sağlandığında yeniden çalışabilir.

semaphore

golang.org/x/sync/semaphore.Semaphore

semaphore paketi, semafor sağlayan bir pakettir. semaphore paketi golang.org/x/sync/semaphore.Semaphore ile oluşturulabilir ve Acquire, Release, TryAcquire metodlarını sağlar.

 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, bir semafor oluşturup Acquire metoduyla semaforu edinen ve Release metoduyla serbest bırakan bir koddur. Bu kodda semaforun nasıl edinilip serbest bırakılacağı gösterilmiştir.

Sonuç

Temel içerik şimdilik burada yeterli olacaktır. Bu makalenin içeriğinden yola çıkarak, goroutine'leri kullanarak eşzamanlılığın nasıl yönetileceğini anlamanız ve bunları gerçekte kullanabilmeniz umulmaktadır. Bu makalenin size yardımcı olmuş olmasını dilerim. Teşekkür ederim.