---
id: 01933e13f963942e4c9bc8c3c89fbc2b
author: snowmerak
title: Go Eşzamanlılık Başlangıç Paketi
description: Go'da eşzamanlılık yönetimi için goroutine, channel, mutex ve diğer çeşitli araç ve tekniklere giriş niteliğinde bir bakış sunulmaktadır.
language: tr
date: 2024-10-18T05:51:30.010680581Z
path: /blog/posts/go-concurrency-starter-pack-z221a3399
category: concurrency
---

## 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.

```go
package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan struct{})
    go func() {
        defer close(ch)
        time.Sleep(1 * time.Second)
        fmt.Println("Hello, World!")
    }()

    fmt.Println("Waiting for goroutine...")
    for range ch {}
}
```

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.

```go
package main

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    go func() {
        <-ctx.Done()
        fmt.Println("Context is done!")
    }()

    time.Sleep(1 * time.Second)

    cancel()

    time.Sleep(1 * time.Second)
}
```

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.

```go
package main

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Second)
    defer cancel()

    ch := make(chan struct{})
    go func() {
        defer close(ch)
        user := getUser(ctx)
        post := getPost(ctx)
        comment := getComment(ctx)

        fmt.Println(user, post, comment)
    }()

    select {
    case <-ctx.Done():
        fmt.Println("Timeout!")
    case <-ch:
        fmt.Println("All data is fetched!")
    }
}
```

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](https://pkg.go.dev/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.

```go
package main

func main() {
    ch := make(chan int)
    go func() {
        ch <- 1
        ch <- 2
        close(ch)
    }()

    for i := range ch {
        fmt.Println(i)
    }
}
```

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.

```go
package main

func main() {
    ch := make(chan int, 2)
    go func() {
        ch <- 1
        ch <- 2
        close(ch)
    }()

    for i := range ch {
        fmt.Println(i)
    }
}
```

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.

```go
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan int, 10)
    ch2 := make(chan int, 10)
    ch3 := make(chan int, 10)

    go func() {
        for {
            ch1 <- 1
            time.Sleep(1 * time.Second)
        }
    }()
    go func() {
        for {
            ch2 <- 2
            time.Sleep(2 * time.Second)
        }
    }()
    go func() {
        for {
            ch3 <- 3
            time.Sleep(3 * time.Second)
        }
    }()

    for i := 0; i < 3; i++ {
        select {
        case v := <-ch1:
            fmt.Println(v)
        case v := <-ch2:
            fmt.Println(v)
        case v := <-ch3:
            fmt.Println(v)
        }
    }
}
```

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.

```go
package main

func main() {
    ch := make(chan int)
    go func() {
        ch <- 1
        ch <- 2
        close(ch)
    }()

    for i := range ch {
        fmt.Println(i)
    }
}
```

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.

```go
package main

func main() {
    ch := make(chan struct{})
    go func() {
        defer close(ch)
        time.Sleep(1 * time.Second)
        fmt.Println("Hello, World!")
    }()

    fmt.Println("Waiting for goroutine...")
    for range ch {}
}
```

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.

```go
package spinlock

import (
    "runtime"
    "sync/atomic"
)

type SpinLock struct {
    lock uintptr
}

func (s *SpinLock) Lock() {
    for !atomic.CompareAndSwapUintptr(&s.lock, 0, 1) {
        runtime.Gosched()
    }
}

func (s *SpinLock) Unlock() {
    atomic.StoreUintptr(&s.lock, 0)
}

func NewSpinLock() *SpinLock {
    return &SpinLock{}
}
```

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.

```go
package main

import (
    "sync"
)

func main() {
    var mu sync.Mutex
    var count int

    go func() {
        mu.Lock()
        count++
        mu.Unlock()
    }()

    mu.Lock()
    count++
    mu.Unlock()

    println(count)
}
```

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 `mutex`tir. `RLock` ve `RUnlock` metodlarıyla okuma kilidi alınıp serbest bırakılabilir.

```go
package cmap

import (
    "sync"
)

type ConcurrentMap[K comparable, V any] struct {
    sync.RWMutex
    data map[K]V
}

func (m *ConcurrentMap[K, V]) Get(key K) (V, bool) {
    m.RLock()
    defer m.RUnlock()

    value, ok := m.data[key]
    return value, ok
}

func (m *ConcurrentMap[K, V]) Set(key K, value V) {
    m.Lock()
    defer m.Unlock()

    m.data[key] = value
}
```

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.

```go
package fakelock

type FakeLock struct{}

func (f *FakeLock) Lock() {}

func (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.

```go
package main

import (
    "sync"
    "sync/atomic"
)

func main() {
    wg := sync.WaitGroup{}
    c := atomic.Int64{}

    for i := 0; i < 100 ; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            c.Add(1)
        }()
    }

    wg.Wait()
    println(c.Load())
}
```

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.

```go
package main

import (
	"fmt"
	"sync"
    "rand"
)

func main() {
	var wg sync.WaitGroup
	arr := [10]int{}

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()

			arr[id] = rand.Intn(100)
		}(i)
	}

	wg.Wait()
	fmt.Println("Done")

    for i, v := range arr {
        fmt.Printf("arr[%d] = %d\n", i, v)
    }
}
```

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.

```go
package main

import (
    "context"
    "fmt"
    "golang.org/x/sync/errgroup"
)

func main() {
    g, ctx := errgroup.WithContext(context.Background())
    _ = ctx

    for i := 0; i < 10; i++ {
        i := i
        g.Go(func() error {
            if i == 5 {
                return fmt.Errorf("error")
            }
            return nil
        })
    }

    if err := g.Wait(); err != nil {
        fmt.Println(err)
    }
}
```

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.

```go
func OnceFunc(f func()) func()
func OnceValue[T any](f func() T) func() T
func 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.

```go
package main

import "sync"

func main() {
    once := sync.OnceFunc(func() {
        println("Hello, World!")
    })

    once()
    once()
    once()
    once()
    once()
}
```

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.

```go
package main

import "sync"

func main() {
    c := 0
    once := sync.OnceValue(func() int {
        c += 1
        return c
    })

    println(once())
    println(once())
    println(once())
    println(once())
    println(once())
}
```

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.

```go
package main

import "sync"

func main() {
    c := 0
    once := sync.OnceValues(func() (int, int) {
        c += 1
        return c, c
    })

    a, b := once()
    println(a, b)
    a, b = once()
    println(a, b)
    a, b = once()
    println(a, b)
    a, b = once()
    println(a, b)
    a, b = once()
    println(a, b)
}
```

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.

```go
package main

import (
    "sync"
    "sync/atomic"
)

func main() {
    wg := sync.WaitGroup{}
    c := atomic.Int64{}

    for i := 0; i < 100 ; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            c.Add(1)
        }()
    }

    wg.Wait()
    println(c.Load())
}
```

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.

```go
package main

import (
    "sync"
)

func main() {
    c := sync.NewCond(&sync.Mutex{})
    ready := false

    go func() {
        c.L.Lock()
        ready = true
        c.Signal()
        c.L.Unlock()
    }()

    c.L.Lock()
    for !ready {
        c.Wait()
    }
    c.L.Unlock()

    println("Ready!")
}
```

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.

```go
package queue

import (
    "sync"
    "sync/atomic"
)

type Node[T any] struct {
    Value T
    Next  *Node[T]
}

type Queue[T any] struct {
    sync.Mutex
    Cond *sync.Cond
    Head *Node[T]
    Tail *Node[T]
    Len  int
}

func New[T any]() *Queue[T] {
    q := &Queue[T]{}
    q.Cond = sync.NewCond(&q.Mutex)
    return q
}

func (q *Queue[T]) Push(value T) {
    q.Lock()
    defer q.Unlock()

    node := &Node[T]{Value: value}
    if q.Len == 0 {
        q.Head = node
        q.Tail = node
    } else {
        q.Tail.Next = node
        q.Tail = node
    }
    q.Len++
    q.Cond.Signal()
}

func (q *Queue[T]) Pop() T {
    q.Lock()
    defer q.Unlock()

    for q.Len == 0 {
        q.Cond.Wait()
    }

    node := q.Head
    q.Head = q.Head.Next
    q.Len--
    return node.Value
}
```

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.

```go
package main

import (
    "context"
    "fmt"
    "golang.org/x/sync/semaphore"
)

func main() {
    s := semaphore.NewWeighted(1)

    if s.TryAcquire(1) {
        fmt.Println("Acquired!")
    } else {
        fmt.Println("Not Acquired!")
    }

    s.Release(1)
}
```

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.
