Tamponlu ve Tamponsuz Kanallar
Coddy'nin GO Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 61 / 107.
Şu ana kadar kullandığımız kanallar tamponsuzdur (unbuffered)—değer tutma kapasiteleri yoktur. Bir gönderme işlemi, başka bir goroutine alana kadar bloklanır ve bunun tersi de geçerlidir. Tamponlu kanallar (buffered channels), dahili depolama alanı ekleyerek gönderme işlemlerinin anında bir alıcı olmadan tamamlanmasına olanak tanır.
make fonksiyonuna bir kapasite ileterek tamponlu bir kanal oluşturun:
// Tamponsuz - kapasite 0
unbuffered := make(chan int)
// Tamponlu - kapasite 3
buffered := make(chan int, 3)Tamponlu bir kanal ile, gönderimler yalnızca tampon dolu olduğunda engellenir ve alımlar yalnızca tampon boş olduğunda engellenir:
ch := make(chan string, 2)
ch <- "first" // bloklamaz - tamponda yer var
ch <- "second" // bloklamaz - tamponda yer var
// ch <- "third" bloklar - tampon dolu
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"Tamponda şu anda kaç öğe olduğunu kontrol etmek için len(ch), toplam kapasite için ise cap(ch) kullanın.
Her birinin ne zaman kullanılacağı: Tamponsuz kanallar (unbuffered channels) sıkı bir senkronizasyon sağlar; gönderici, alıcının değeri aldığını bilir. Tamponlu kanallar (buffered channels), gönderici ve alıcı zamanlamasını birbirinden ayırır; bu, üreticilerin ve tüketicilerin farklı hızlarda çalıştığı durumlarda yararlıdır. Ancak, tamponlu kanallar senkronizasyon hatalarını gizleyebilir, bu nedenle tamponlama için özel bir nedeniniz yoksa tamponsuz olanları tercih edin.
Görev
KolayTamponlanmış (buffered) ve tamponlanmamış (unbuffered) kanallar arasındaki farkı gösteren bir toplu işlemci (batch processor) oluşturalım. Üreticinin (producer) öğeleri bir işlemciye gönderdiği, zamanlamalarını birbirinden ayırmak ve üreticinin önceden çalışmasına izin vermek için tamponlanmış kanalların kullanıldığı bir sistem oluşturacaksınız.
Kodunuzu iki dosya halinde düzenleyeceksiniz:
processor.go: Kanalları kullanarak toplu işlem mantığınızı tanımlayın.ID(int) veValue(string) alanlarına sahip birItemyapısı (struct) oluşturun.İki fonksiyon uygulayın:
Producer(items []Item, out chan Item)- Her bir öğeyi çıktı kanalına gönderir. Her öğeyi gönderdikten sonra şunu yazdırın:Produced item [ID]. Tüm öğeler gönderildikten sonra,len(out)kullanarak tamponda bekleyen mevcut öğe sayısını şu formatta yazdırın:Buffer has [count] items. Ardından kanalı kapatın.Consumer(in chan Item) []string- Giriş kanalından öğeleri alır ve bunları formatlanmış dizelerden (strings) oluşan bir dilim (slice) içinde toplar. Alınan her öğe için dize şu şekilde olmalıdır:Consumed: [ID] - [Value]. Kanal kapatıldığında dilimi döndürün.
main.go: Tamponlanmış kanalı kurun ve üretici ile tüketiciyi koordine edin.Tampon kapasitesini, ardından öğe sayısını ve son olarak her öğenin ID'sini ve değerini okuyun. Belirtilen kapasiteye sahip tamponlanmış bir kanal oluşturun. Tamponu doldurmak için önce Üreticiyi (Producer) çalıştırın (bir goroutine olarak değil), ardından tüm öğeleri işlemek için Tüketiciyi (Consumer) çalıştırın. Tüketilen her sonucu ayrı bir satıra yazdırın.
Aşağıdaki girdiler sağlanacaktır:
- Satır 1: Tampon kapasitesi (tam sayı)
- Satır 2: Öğe sayısı (tam sayı)
- Sonraki satırlar: Her öğe için iki satır - öğe ID'si (tam sayı), ardından değeri (dize)
Örneğin, şu girdi verildiğinde:
3
3
1
apple
2
banana
3
cherryÇıktınız şu şekilde olmalıdır:
Produced item 1
Produced item 2
Produced item 3
Buffer has 3 items
Consumed: 1 - apple
Consumed: 2 - banana
Consumed: 3 - cherry3 birimlik bir tampon kapasitesiyle, üreticinin engellenmeden (blocking) 3 öğenin tamamını nasıl gönderebildiğine ve tüketici başlamadan önce tamponun bekleyen 3 öğeyi nasıl gösterdiğine dikkat edin. Eğer tampon öğe sayısından daha küçük olsaydı, üretici yer açılmasını beklerken engellenecekti.
Kopya kağıdı
Go'daki kanallar tamponsuz (kapasite 0) veya tamponlu (dahili depolama ile) olabilir.
Kanalları make ile oluşturun:
// Tamponsuz - kapasite 0
unbuffered := make(chan int)
// Tamponlu - kapasite 3
buffered := make(chan int, 3)Tamponlu kanal davranışı:
- Gönderimler yalnızca tampon dolu olduğunda engellenir
- Alımlar yalnızca tampon boş olduğunda engellenir
ch := make(chan string, 2)
ch <- "first" // engellenmez - tamponda yer var
ch <- "second" // engellenmez - tamponda yer var
// ch <- "third" engellenirdi - tampon dolu
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"Tampon durumunu kontrol edin:
len(ch)- şu anda tamponda bulunan öğe sayısıcap(ch)- toplam tampon kapasitesi
Ne zaman kullanılır:
- Tamponsuz: Sıkı senkronizasyon—gönderici, alıcının değeri aldığını bilir
- Tamponlu: Farklı hızlarda çalıştıklarında gönderici ve alıcı zamanlamasını birbirinden ayırın
Kendin dene
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Tampon kapasitesini oku
line, _ := reader.ReadString('\n')
bufferCapacity, _ := strconv.Atoi(strings.TrimSpace(line))
// Öğe sayısını oku
line, _ = reader.ReadString('\n')
numItems, _ := strconv.Atoi(strings.TrimSpace(line))
// Her bir öğenin kimliğini ve değerini oku
items := make([]Item, numItems)
for i := 0; i < numItems; i++ {
line, _ = reader.ReadString('\n')
id, _ := strconv.Atoi(strings.TrimSpace(line))
line, _ = reader.ReadString('\n')
value := strings.TrimSpace(line)
items[i] = Item{ID: id, Value: value}
}
// YAPILACAK: Belirtilen kapasiteye sahip tamponlu bir kanal oluşturun
// YAPILACAK: Tamponu doldurmak için Üreticiyi (goroutine olarak değil) çalıştırın
// YAPILACAK: Tüm öğeleri işlemek için Tüketiciyi çalıştırın
// YAPILACAK: Tüketilen her sonucu ayrı bir satıra yazdırın
}
Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.
Nesne Yönelimli Programlama bölümündeki tüm dersler
1Go OOP Temelleri
Harici DosyalarGo Çalışma Alanı ve ModüllerPaketler ve İçe AktarmalarDışa Aktarılan ve Aktarılmayan İsimlerGo'da OOP'ye GirişSınıf Olarak Struct'larStruct'larda Metot TanımlamaPointer ve Değer AlıcılarStruct BaşlatmaYapıcı FonksiyonlarÖzet - Basit Hesap Makinesi4Arayüzler
Arayüzlere GirişÖrtük UygulamaSözleşme Olarak ArayüzBoş Arayüz (any)Tür OnaylamaTür SeçimiArayüz BileşimiStringer ve Error ArayüzleriÖzet - Şekil Hesaplayıcı7Kapsülleme
Dışa Aktarılan ve Aktarılmayan AlanlarPaket Seviyesinde KapsüllemeGetter ve Setter MetotlarıGo'da Bilgi GizlemeÖzet - Öğrenci Kayıtları10Generics (Go 1.18+)
Generics'e GirişTür ParametreleriTür KısıtlamalarıGeneric Struct'larGeneric Metotlar İçin Geçici ÇözümÖzet - Generic Koleksiyon13Tasarım Kalıpları Bölüm 1
Tasarım Kalıplarına GirişSingleton KalıbıFactory KalıbıAbstract Factory KalıbıObserver KalıbıStrategy Kalıbı2Türler ve Struct'lar: Derinlemesine İnceleme
Temel ve Bileşik TürlerÖzel Tür TanımlamalarıStruct EtiketleriAnonim Struct'larİç İçe Geçmiş Struct'larSıfır Değerler ve VarsayılanlarÖzet - Rehber5Kalıtım Yerine Kompozisyon
Go'da Neden Kalıtım YokStruct Gömme TemelleriMetot YükseltmeBirden Fazla Struct GömmeGömme vs AgregasyonGömülü Metotları GölgelemeÖzet - Çalışan Hiyerarşisi8Hata Yönetimi ve OOP
error ArayüzüÖzel Hata TürleriHata Sarmalama (fmt.Errorf)Sentinel Hatalarerrors.Is() ve errors.As()Panic, Defer ve RecoverÖzet - Dosya Ayrıştırıcı3İşaretçiler ve Bellek
Go'da İşaretçi TemelleriStruct İşaretçileriDeğer ile Geçme ve Referans ile Geçmenew() FonksiyonuGo'da Garbage CollectionÖzet - Linked List Oluşturucu6Go'da Polimorfizm
Interface'ler ile PolimorfizmGo'da Duck TypingInterface Karşılama KurallarıPolimorfik KoleksiyonlarDependency InjectionÖzet - Payment Processor9Eşzamanlılık ve OOP
Goroutine TemelleriKanallar ve İletişimTamponlu ve Tamponsuz KanallarSelect İfadesisync.Mutex ve sync.RWMutexsync.WaitGroupThread-Safe Struct TasarımıÖzet - Worker Pool