Goroutine'ler ve kanallar, Go'da eşzamanlılığın vitrinidir. Ama her problem kanallarla zarif biçimde çözülmez. Bir sayaç, bir önbellek, bir yapılandırma haritası — birden çok goroutine'in aynı veriye erişmesi gereken durumlar vardır ve burada doğru araç paylaşımlı belleği korumaktır.
Go'nun meşhur sözü şudur: "Belleği paylaşarak iletişim kurma; iletişim kurarak belleği paylaş." Bu söz sık sık "mutex kullanma" diye yanlış anlaşılır. Oysa söylediği şey, varsayılan aracın kanallar olması gerektiğidir. Standart kütüphanenin içinde sync paketi durur ve Go'nun kendi kaynak kodu mutex'lerle doludur. Doğru soru "hangisi daha iyi" değil, "bu problemin şekli hangisine uyuyor" sorusudur. Veri bir sahipten diğerine akıyorsa kanal kullan; birden çok goroutine aynı yapıya erişiyorsa kilit kullan.
Bu derste yarış durumlarının neden bu kadar sinsi olduğunu, kritik bölgeyi mutex'le nasıl koruyacağını, okuma ağırlıklı yüklerde RWMutex'in ne kazandırdığını, tek seferlik başlatmayı, kilitsiz atomik işlemleri, sync.Map ile sync.Pool'un dar kullanım alanlarını ve kilit sıralamasıyla kilitlenmelerden (deadlock) nasıl kaçınacağını öğreneceksin.
Yarış durumu nedir?
Bir yarış durumu (race condition), iki goroutine'in aynı belleğe eşzamanlı eriştiği ve en az birinin yazdığı durumdur. Sorun, count++ gibi masum görünen bir satırın aslında üç ayrı adımdan oluşmasıdır: oku, bir ekle, geri yaz. İki goroutine bu adımları iç içe geçirirse bir artırma kaybolur.
goroutine A goroutine B count
oku → 5 5
oku → 5 5
5+1 = 6 5
5+1 = 6 5
yaz 6 6
yaz 6 6 ← iki artırma, sonuç bir arttıpackage main
import (
"fmt"
"sync"
)
func main() {
count := 0
var wg sync.WaitGroup
for range 1000 {
wg.Go(func() {
count++ // YARIŞ DURUMU: korumasız paylaşımlı yazma
})
}
wg.Wait()
fmt.Println("beklenen 1000, gerçek:", count) // her çalıştırmada farklı olabilir
}Bu programın çıktısı her çalıştırmada değişebilir; bazen 1000 bile çıkabilir. İşte bu, yarış durumlarını tehlikeli kılan şeydir: Testlerde görünmez, üretimde ortaya çıkar.
Go 1.25 ile birlikte sync.WaitGroup tipine Go metodu eklendi: wg.Add(1) ve defer wg.Done() yazmak yerine doğrudan wg.Go(func() { ... }) diyebilirsin. Bu derste bu yeni biçimi kullanacağız.
sync.Mutex ve kritik bölge
Mutex (mutual exclusion, karşılıklı dışlama), aynı anda yalnızca bir goroutine'in geçmesine izin veren bir kapıdır. Lock ile kapıyı kapatır, Unlock ile açarsın. İkisinin arasında kalan koda kritik bölge denir.
package main
import (
"fmt"
"sync"
)
type Counter struct {
mu sync.Mutex
values map[string]int
}
func NewCounter() *Counter {
return &Counter{values: make(map[string]int)}
}
func (c *Counter) Inc(key string) {
c.mu.Lock()
defer c.mu.Unlock() // panik olsa bile kilit açılır
c.values[key]++
}
func (c *Counter) Get(key string) int {
c.mu.Lock()
defer c.mu.Unlock()
return c.values[key]
}
func main() {
c := NewCounter()
var wg sync.WaitGroup
for range 500 {
wg.Go(func() {
c.Inc("istek")
c.Inc("bayt")
})
}
wg.Wait()
fmt.Println("istek:", c.Get("istek"))
fmt.Println("bayt:", c.Get("bayt"))
}istek: 500 bayt: 500
Birkaç kural bu deseni güvenli kılar:
Kilidi veriyle birlikte tut. Mutex'i koruduğu alanın hemen üstüne yaz. Böyle yazılmış bir struct'a bakan herkes neyin korunduğunu görür.
defer ile aç. Fonksiyon ortasında dönen bir yol ya da bir panik, açılmamış bir kilit bırakırsa program kilitlenir. defer bunu imkânsız kılar.
Kritik bölgeyi kısa tut. Kilit altındayken ağ çağrısı yapmak, dosya okumak veya başka bir kilit almak bekleyen herkesi durdurur.
Mutex içeren struct'ı kopyalama. Kopyalanan kilit artık aynı kapıyı korumaz. Bu yüzden böyle tiplerin metotları işaretçi alıcı olmalıdır; go vet kopyalamayı yakalar.
Sıfır değer hazırdır. var mu sync.Mutex doğrudan kullanılabilir; ayrıca başlatmaya gerek yoktur.
sync.RWMutex: okuma ağırlıklı yükler
Bir veri yapısına çok sık okuma, seyrek yazma yapılıyorsa normal mutex gereksiz yere sıralama yaratır: Okuyucular birbirini engellemez ki. RWMutex, aynı anda birden çok okuyucuya ya da tek bir yazara izin verir.
package main
import (
"fmt"
"sync"
)
type Config struct {
mu sync.RWMutex
settings map[string]string
}
func NewConfig() *Config {
return &Config{settings: map[string]string{"tema": "koyu"}}
}
// Okuyucular birbirini engellemez
func (c *Config) Get(key string) (string, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
v, ok := c.settings[key]
return v, ok
}
// Yazar, tüm okuyucuları bekletir
func (c *Config) Set(key, value string) {
c.mu.Lock()
defer c.mu.Unlock()
c.settings[key] = value
}
func main() {
cfg := NewConfig()
var wg sync.WaitGroup
// 100 okuyucu ve 10 yazar aynı anda çalışsın
for range 100 {
wg.Go(func() {
cfg.Get("tema")
})
}
for i := range 10 {
wg.Go(func() {
cfg.Set(fmt.Sprintf("anahtar%d", i), "değer")
})
}
wg.Wait()
theme, _ := cfg.Get("tema")
fmt.Println("tema:", theme)
fmt.Println("toplam ayar:", len(cfg.settings))
}tema: koyu toplam ayar: 11
RWMutex her zaman daha hızlı değildir. Kendi iç muhasebesi normal mutex'ten pahalıdır; kritik bölge çok kısaysa (bir alan okumak gibi) sade mutex daha iyi sonuç verir. Kazanç, okuma oranı yüksek ve okuma süresi kayda değer olduğunda ortaya çıkar. Emin değilsen ölç: go test -bench ile iki sürümü karşılaştırmak birkaç dakikalık iştir.
Ayrıca RLock yeniden girişli değildir: Aynı goroutine içinde okuma kilidini alıp sonra yazma kilidi almaya çalışırsan program kilitlenir.
Tek seferlik başlatma: sync.Once
Pahalı bir kaynağı (veritabanı bağlantısı, derlenmiş bir düzenli ifade, büyük bir tablo) yalnızca ilk ihtiyaç duyulduğunda ve tam olarak bir kez kurmak isteyebilirsin. sync.Once bunu garanti eder:
package main
import (
"fmt"
"sync"
)
var (
once sync.Once
table map[string]int
calls int
)
func getTable() map[string]int {
once.Do(func() {
calls++ // yalnızca bir kez çalışır
table = map[string]int{"bir": 1, "iki": 2, "üç": 3}
})
return table
}
// Go 1.21+: OnceValue daha kısa ve tip güvenlidir
var expensive = sync.OnceValue(func() int {
return 6 * 7
})
func main() {
var wg sync.WaitGroup
for range 50 {
wg.Go(func() {
_ = getTable()
_ = expensive()
})
}
wg.Wait()
fmt.Println("başlatma sayısı:", calls)
fmt.Println("tablo:", getTable()["iki"])
fmt.Println("hesap:", expensive())
}başlatma sayısı: 1 tablo: 2 hesap: 42
Do içindeki fonksiyon panik yaparsa, Once yine de "çalıştırıldı" sayılır ve bir daha denenmez. Hata dönebilen başlatmalar için sync.OnceValues kullanmak (değer ve hatayı birlikte önbelleğe alır) daha uygundur.
sync/atomic: kilitsiz sayaçlar
Tek bir sayıyı korumak için mutex kullanmak, çekiçle ceviz kırmaya benzer. sync/atomic paketi, işlemcinin atomik komutlarını kullanan tipler sunar: atomic.Int64, atomic.Bool, atomic.Pointer[T] ve diğerleri.
package main
import (
"fmt"
"sync"
"sync/atomic"
)
type Stats struct {
requests atomic.Int64
errors atomic.Int64
shutdown atomic.Bool
}
func main() {
s := &Stats{}
var wg sync.WaitGroup
for i := range 1000 {
wg.Go(func() {
s.requests.Add(1)
if i%10 == 0 {
s.errors.Add(1)
}
})
}
wg.Wait()
fmt.Println("istek:", s.requests.Load())
fmt.Println("hata:", s.errors.Load())
// Compare-and-swap: yalnızca beklenen değerdeyse değiştirir
swapped := s.shutdown.CompareAndSwap(false, true)
again := s.shutdown.CompareAndSwap(false, true)
fmt.Println("ilk kapatma isteği kabul edildi mi:", swapped, "| ikincisi:", again)
}istek: 1000 hata: 100 ilk kapatma isteği kabul edildi mi: true | ikincisi: false
Atomik tipler tek bir değeri korur. Birden fazla alanın birlikte tutarlı kalması gerekiyorsa atomikler yetmez; mutex gerekir. Örneğin bir sayaç ile bir zaman damgasını birlikte güncellemen gerekiyorsa, ikisini ayrı atomiklerle güncellemek arada tutarsız bir an bırakır.
CompareAndSwap (CAS), kilitsiz programlamanın temel taşıdır: "Değer hâlâ beklediğim gibiyse değiştir, değilse bana söyle." Yukarıdaki örnekte olduğu gibi, bir işlemin yalnızca bir kez yapılmasını garanti etmek için idealdir.
sync.Map ve sync.Pool
İki özel amaçlı araç daha vardır ve ikisi de yanlış yerde kullanılmaya çok müsaittir.
sync.Map, kilitsiz okumaya iyileştirilmiş bir haritadır. Ama genel amaçlı bir eşzamanlı map değildir. Yalnızca iki senaryoda kazandırır: anahtarlar bir kez yazılıp defalarca okunduğunda (önbellek gibi) ve farklı goroutine'ler ayrı anahtar kümeleriyle çalıştığında. Diğer tüm durumlarda mutex'le korunan sade bir harita hem daha hızlı hem de tip güvenlidir — çünkü sync.Map anahtar ve değerleri any olarak tutar.
sync.Pool, kısa ömürlü nesneleri yeniden kullanmak için bir havuzdur. Amaç çöp toplayıcı üzerindeki baskıyı azaltmaktır. Havuzdaki nesneler herhangi bir anda silinebilir, bu yüzden kalıcı veri saklamak için kullanılamaz. Tipik kullanımı, büyük tamponları yeniden kullanmaktır.
package main
import (
"bytes"
"fmt"
"sync"
)
var bufPool = sync.Pool{
New: func() any { return new(bytes.Buffer) },
}
func render(name string) string {
buf := bufPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset() // havuza temiz döndür
bufPool.Put(buf)
}()
buf.WriteString("Merhaba, ")
buf.WriteString(name)
buf.WriteString("!")
return buf.String()
}
func main() {
var wg sync.WaitGroup
results := make([]string, 4)
for i, name := range []string{"Ali", "Ayşe", "Mehmet", "Zeynep"} {
wg.Go(func() {
results[i] = render(name) // her goroutine kendi indeksine yazar: yarış yok
})
}
wg.Wait()
for _, r := range results {
fmt.Println(r)
}
}Merhaba, Ali! Merhaba, Ayşe! Merhaba, Mehmet! Merhaba, Zeynep!
Dilimin farklı indekslerine yazmak yarış durumu oluşturmaz, çünkü her goroutine ayrı bir bellek hücresine dokunur. Dilime append yapmak ise yarış oluşturur; çünkü append uzunluğu ve muhtemelen işaretçiyi değiştirir.
Kilitlenme (deadlock) ve kilit sıralaması
Kilitlenme, iki goroutine'in birbirinin bıraktığı kilidi beklemesidir ve programı sonsuza kadar durdurur:
goroutine A goroutine B
Lock(hesap1) Lock(hesap2)
Lock(hesap2) ⏳ Lock(hesap1) ⏳
↑ ↑
└── ikisi de diğerini bekliyor ──┘Çözüm şaşırtıcı derecede basittir: Kilitleri her zaman aynı sırada al. Sıralama için değişmez bir ölçüt seçersin — genelde nesnenin kimliği:
package main
import (
"fmt"
"sync"
)
type Account struct {
id int
mu sync.Mutex
balance int
}
// Transfer, kilitleri her zaman küçük id'den büyüğe doğru alır.
func Transfer(from, to *Account, amount int) {
first, second := from, to
if from.id > to.id {
first, second = to, from
}
first.mu.Lock()
defer first.mu.Unlock()
second.mu.Lock()
defer second.mu.Unlock()
from.balance -= amount
to.balance += amount
}
func main() {
a := &Account{id: 1, balance: 1000}
b := &Account{id: 2, balance: 1000}
var wg sync.WaitGroup
for range 100 {
wg.Go(func() { Transfer(a, b, 10) })
wg.Go(func() { Transfer(b, a, 10) })
}
wg.Wait()
fmt.Println("a:", a.balance, "b:", b.balance)
fmt.Println("toplam korundu mu:", a.balance+b.balance == 2000)
}a: 1000 b: 1000 toplam korundu mu: true
Kilitlenmeyi önlemenin diğer yolları: kilit altındayken başka bir bileşene çağrı yapmamak (özellikle geri çağırımlara), kilit hiyerarşisini belgelemek ve mümkünse tek bir kilitle yetinmek. Go çalışma zamanı, tüm goroutine'ler uyuduğunda "all goroutines are asleep - deadlock!" diyerek programı sonlandırır; ama bazı goroutine'ler çalışmaya devam ediyorsa bu tespiti yapamaz.
Hangi aracı ne zaman?
Eşzamanlı bir problemle karşılaştığında sırayla şu soruları sor.
Paylaşım gerçekten gerekli mi? En hızlı ve en güvenli senkronizasyon, hiç olmayanıdır. Her goroutine kendi verisiyle çalışıp sonucu en sonda birleştirebiliyorsa hiçbir kilide ihtiyacın kalmaz. Sonuçları önceden boyutlandırılmış bir dilimin ayrı indekslerine yazmak ya da her goroutine'in kendi yerel toplamını tutup sonunda tek seferde eklemesi bu yaklaşımın tipik örnekleridir.
Veri akıyor mu, paylaşılıyor mu? Bir değerin sahipliği bir goroutine'den diğerine geçiyorsa kanal doğru araçtır: Sahiplik devri kodun yapısında görünür hâle gelir. Aynı yapıya birden çok goroutine sürekli erişiyorsa kanal kullanmak yapay bir sunucu goroutine'i yaratmaya zorlar; burada kilit daha dürüst bir çözümdür.
Korunan şey tek bir sayı mı? Sayaç, bayrak ya da tek bir işaretçi söz konusuysa atomik tipler hem daha hızlıdır hem de kilit açmayı unutma riskini ortadan kaldırır. Ama iki değer arasında tutarlılık gerekiyorsa atomikler yetmez.
Okuma yazmadan çok mu baskın? Öyleyse okuma-yazma kilidi ölçülebilir kazanç sağlayabilir. Değilse sade mutex hem daha basit hem genelde daha hızlıdır.
Başlatma bir kez mi yapılmalı? Tek seferlik kurulum için ayrı bir araç vardır ve onu kullanmak, elle yazılmış bayrak kontrollerinden hem daha kısa hem de doğrudur.
Bu sorular şu sırayla sorulduğunda, çoğu problem ilk ikisinde çözülür. Geri kalanlar için en sade aracı seç ve karmaşıklığı ancak ölçüm gerektirdiğinde artır.
Sık yapılan hatalar
- Kilidi
deferolmadan açmak. Erken dönüş veya panik, açılmamış kilit bırakır ve program kilitlenir. - Mutex içeren struct'ı kopyalamak. Değer alıcılı metotlar ya da değer olarak geçirme kilidi işlevsiz kılar;
go vetuyarır. - Okuma kilidi altında yazmaya çalışmak.
RLockyazmaya izin vermez; ayrıca aynı goroutine'deRLocksonrasıLockalmak kilitlenir. - Atomiklerle çok alanlı tutarlılık kurmaya çalışmak. Her atomik kendi başına tutarlıdır; birlikte değil.
sync.Map'i genel amaçlı eşzamanlı harita sanmak. Çoğu durumda mutex'li sade harita daha hızlıdır.sync.Pool'da kalıcı veri saklamak. Havuz içeriği herhangi bir anda silinebilir.- Kilit altındayken uzun iş yapmak. Ağ çağrısı, dosya işlemi ve geri çağırımları kritik bölgenin dışına taşı.
- Yarış dedektörünün sessizliğini kanıt saymak. Dedektör yalnızca gerçekleşen yarışları görür.
Alıştırmalar
Eşzamanlı olarak artırılabilen bir SafeCounter tipi yaz: Inc, Add(n) ve Value metotları olsun. 1000 goroutine'den artır ve sonucun tam olarak beklenen değerde olduğunu göster. Aynı işi atomic.Int64 ile de yaparak iki yaklaşımı karşılaştır.
İpucu
sync.WaitGroup.Go ile goroutine başlat; her iki sayacı da aynı döngüde artırabilirsin.
Çözümü göster
package main
import (
"fmt"
"sync"
"sync/atomic"
)
type SafeCounter struct {
mu sync.Mutex
value int
}
func (c *SafeCounter) Inc() { c.Add(1) }
func (c *SafeCounter) Add(n int) {
c.mu.Lock()
defer c.mu.Unlock()
c.value += n
}
func (c *SafeCounter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}
func main() {
mutexCounter := &SafeCounter{}
var atomicCounter atomic.Int64
var wg sync.WaitGroup
for range 1000 {
wg.Go(func() {
mutexCounter.Inc()
atomicCounter.Add(1)
})
}
wg.Wait()
fmt.Println("mutex sayacı: ", mutexCounter.Value())
fmt.Println("atomik sayaç:", atomicCounter.Load())
fmt.Println("ikisi eşit mi:", int64(mutexCounter.Value()) == atomicCounter.Load())
}mutex sayacı: 1000 atomik sayaç: 1000 ikisi eşit mi: true
İki yaklaşım da doğru sonucu verir. Tek bir sayı için atomik sürüm hem daha kısa hem daha hızlıdır; ancak sayaçla birlikte başka bir alanı da tutarlı tutman gerekseydi mutex zorunlu olurdu.
RWMutex kullanan bir Cache tipi yaz: Get, Set ve Len metotları olsun. Ayrıca "varsa döndür, yoksa hesapla ve sakla" davranışını sağlayan bir GetOrCompute metodu ekle. Çok sayıda goroutine'den aynı anda kullanıp sonucu doğrula.
İpucu
GetOrCompute önce okuma kilidiyle bakmalı, bulamazsa yazma kilidi alıp tekrar kontrol etmelidir: İki goroutine aynı anda hesaplamaya karar vermiş olabilir.
Çözümü göster
package main
import (
"fmt"
"sync"
)
type Cache struct {
mu sync.RWMutex
data map[string]int
calls int // hesaplama fonksiyonunun kaç kez çağrıldığı
}
func NewCache() *Cache {
return &Cache{data: make(map[string]int)}
}
func (c *Cache) Get(key string) (int, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
v, ok := c.data[key]
return v, ok
}
func (c *Cache) Set(key string, value int) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[key] = value
}
func (c *Cache) Len() int {
c.mu.RLock()
defer c.mu.RUnlock()
return len(c.data)
}
func (c *Cache) GetOrCompute(key string, compute func() int) int {
if v, ok := c.Get(key); ok {
return v
}
c.mu.Lock()
defer c.mu.Unlock()
if v, ok := c.data[key]; ok { // çift kontrol: araya başka goroutine girmiş olabilir
return v
}
c.calls++
v := compute()
c.data[key] = v
return v
}
func main() {
cache := NewCache()
var wg sync.WaitGroup
for range 200 {
wg.Go(func() {
cache.GetOrCompute("kare", func() int { return 12 * 12 })
cache.GetOrCompute("küp", func() int { return 5 * 5 * 5 })
})
}
wg.Wait()
kare, _ := cache.Get("kare")
kup, _ := cache.Get("küp")
fmt.Println("kare:", kare, "küp:", kup)
fmt.Println("anahtar sayısı:", cache.Len())
fmt.Println("hesaplama çağrısı:", cache.calls)
}kare: 144 küp: 125 anahtar sayısı: 2 hesaplama çağrısı: 2
Çift kontrol (double-checked locking) deseni burada zorunludur: İlk kontrolü okuma kilidiyle yapıp kilidi bıraktığın an, başka bir goroutine aynı anahtarı hesaplamış olabilir. Yazma kilidini aldıktan sonra tekrar bakmak, hesabın yalnızca bir kez yapılmasını sağlar.
Aynı anda en fazla N işin çalışmasına izin veren bir semafor yaz (tamponlu kanal kullanarak) ve 20 işi en fazla 3'lü gruplar hâlinde çalıştır. Aynı anda çalışan iş sayısının hiçbir zaman 3'ü aşmadığını, atomik bir sayaç ve tepe değeri takip ederek kanıtla.
İpucu
Tamponlu bir kanal (make(chan struct{}, n)) doğal bir semafordur: kanala yazmak "yer kap", okumak "yeri bırak" demektir. Tepe değeri güncellemek için CompareAndSwap döngüsü kullan.
Çözümü göster
package main
import (
"fmt"
"sync"
"sync/atomic"
)
type Semaphore chan struct{}
func NewSemaphore(n int) Semaphore { return make(Semaphore, n) }
func (s Semaphore) Acquire() { s <- struct{}{} }
func (s Semaphore) Release() { <-s }
func main() {
const limit = 3
sem := NewSemaphore(limit)
var running, peak, done atomic.Int64
var wg sync.WaitGroup
for i := range 20 {
wg.Go(func() {
sem.Acquire()
defer sem.Release()
current := running.Add(1)
for { // tepe değeri güvenle güncelle
old := peak.Load()
if current <= old || peak.CompareAndSwap(old, current) {
break
}
}
// işi simüle et: küçük bir hesap
sum := 0
for j := range 1000 {
sum += j * i
}
_ = sum
done.Add(1)
running.Add(-1)
})
}
wg.Wait()
fmt.Println("tamamlanan iş:", done.Load())
fmt.Println("tepe değeri sınır içinde mi:", peak.Load() <= limit)
fmt.Println("en az bir iş paralel çalıştı mı:", peak.Load() >= 1)
fmt.Println("çalışan kalan:", running.Load())
}tamamlanan iş: 20 tepe değeri sınır içinde mi: true en az bir iş paralel çalıştı mı: true çalışan kalan: 0
Tepe değerini doğrudan yazdırmadığımıza dikkat et: Aynı anda kaç işin çalıştığı zamanlamaya bağlıdır ve her çalıştırmada değişir. Doğrulanabilir olan şey, bu sayının hiçbir zaman sınırı aşmamasıdır; çıktıyı da bu değişmez üzerine kurduk.
Tamponlu kanal, kapasitesi kadar yazmaya izin verir; kapasite dolduğunda Acquire bloklanır. Böylece hiçbir ek kütüphaneye ihtiyaç duymadan sayısal bir semafor elde edersin. Tepe değeri güncellerken kullanılan CAS döngüsü klasik bir kilitsiz desendir: Değeri oku, daha büyükse değiştirmeyi dene, araya biri girdiyse baştan başla. Bu deseni ve worker pool'u Context ve Eşzamanlılık Desenleri dersinde genişleteceğiz.
Kısa sınav
c.mu.Lock() çağrısından sonra defer c.mu.Unlock() yazmak neden önerilir?
sync.RWMutex ne zaman sade sync.Mutexten daha iyidir?
Aşağıdakilerden hangisi yarış durumu oluşturmaz?
sync.Once içindeki fonksiyon panik yaparsa ne olur?
İki hesabı kilitleyen bir transfer fonksiyonunda kilitlenmeyi ne önler?
atomic.Int64 yerine mutex kullanmak hangi durumda zorunludur?
Özet
- Yarış durumu, en az biri yazan iki eşzamanlı erişimden doğar;
-racebayrağı bunları yakalar ama yokluğu güvenlik kanıtı değildir. sync.Mutexkritik bölgeyi korur; kilidi koruduğu verinin yanına yaz,deferile aç ve bölgeyi kısa tut.- Mutex içeren tipler kopyalanmamalıdır; metotları işaretçi alıcı olmalıdır.
sync.RWMutexçok okuyuculu, kayda değer uzunlukta kritik bölgelerde kazandırır; kısa bölgelerde sade mutex daha hızlıdır.sync.Oncevesync.OnceValuetek seferlik başlatmayı garanti eder.sync/atomictipleri tek bir değeri kilitsiz korur;CompareAndSwapkoşullu güncellemenin temel taşıdır.sync.Mapvesync.Pooldar kullanım alanlarına sahiptir; varsayılan çözüm değildirler.- Kilitlenmeden kaçınmanın en etkili yolu, kilitleri her zaman aynı sırada almaktır.
- En iyi senkronizasyon, hiç paylaşmamaktır: Veriyi bölüştür, sonuçları sonda birleştir.