Kısım II · İnşaat · Bölüm 36

Go: Neden Go

Arka ucun dilini okumak (The Go language and why we chose it)

Neden burada · Neye dayanıyor

Neden burada: HTTP sözleşmesini tanıdık; şimdi onu yerine getirecek arka ucun dilini seçilmiş gerekçeleriyle okuyacağız. Amaç dili ezberlemek değil, Go dosyasındaki temel kararları ayırt etmek.

Neye dayanıyor: Bölüm 21: tip ve çalışma biçimi; Bölüm 9: derleme ve fonksiyonlar; Bölüm 14: eşzamanlılık ve yarış durumu.

  1. Kat 10Bizim uygulamamız: Go arka uç, Postgres ve istemciBu bölüm: Go: Neden Go. Bölüm 1'in kat planı üzerindeyiz.
  2. Kat 9 ve 8Web ve ağKullanıcı arayüzünden gelen istekler HTTP ile taşınır.
  3. Kat 7 ve 5İşletim sistemi, diller ve derleyiciProgram süreç olur; derleyici Go metnini çalıştırılabilir dosyaya çevirir.
  4. Kat 0–4 ve 6Fiziksel temel, makine dili ve açılışBellek, depolama ve işlemci alttaki işi sürdürür; burada yeni donanım yok.

Bölüm sonunda paket, yapı, metot, arayüz, hata dönüşü ve goroutine gördüğünde her birinin hangi sorunu çözdüğünü söyleyebileceksin. Küçük programlar gerçek çalıştırmaya uygun; henüz tam HTTP sunucusu yazmıyoruz.

36.1 Go'nun doğuşu ve tasarım hedefleri

Bölüm 21'de dillerin haritasını çıkarmıştık; burada hepsini yeniden tanımlamayacağız. Go'yu o haritada üç işarete bağla: statik tipli, makine koduna derlenen, çöp toplayıcılı. Arka ucu Go ile yazmak, telefonda Go çalıştıracağımız anlamına gelmez. Expo tarafında TypeScript, sunucuda Go vardır. Aralarında Go fonksiyon çağrıları değil, Bölüm 35'teki HTTP ve JSON mesajları dolaşır.1

Go'nun tasarımı Google'da Robert Griesemer, Rob Pike ve Ken Thompson'ın 2007'deki çalışmalarıyla başladı. 10 Kasım 2009'da kamuya açık, açık kaynak proje oldu. Dolayısıyla “Go 2009'da doğdu” cümlesi, başlangıç tarihi ile halka açılmayı ayırmadığında eksik kalır. Go FAQ'nun tarih anlatısı bu iki aşamayı açıkça ayırır. Burada tarih ezberlemek için değil, dilin hangi probleme cevap olarak ortaya çıktığını anlamak için duruyoruz.1

Problem yalnız “programı hızlı çalıştırmak” değildi. Büyük yazılım projelerinin karmaşık bağımlılıkları, uzun derleme döngüleri ve çok çekirdekli makinelerde işlerin düzenlenmesi de sorundu. Tasarım hedefleri; okunabilir ve sade bir dil, hızlı geliştirme/derleme döngüsü, otomatik bellek yönetimi ve eşzamanlılığı dilin doğal parçası yapmaktı. Bunlar tasarım niyetidir; her Go programının her koşulda başka dilden hızlı olacağına ilişkin ölçüm değildir. Bir hedefi performans garantisi diye okumayacağız.1

Çalışma zamanı (runtime), Go programının çalışırken yararlandığı dil hizmetleridir: çöp toplama, goroutine düzenleme ve yığın yönetimi gibi. Go'nun çalışma zamanı vardır; ama bu, Java'nın ara kodunu çalıştıran JVM ile aynı model olduğu anlamına gelmez. Standart Go araç zinciri programı önceden yerel makine koduna derler; çalışma zamanı hizmetleri de programın parçasıdır. “Go için ayrıca yorumlayıcı kurmuyorum” ile “Go'nun çalışma zamanı yok” farklı iddialardır; ikincisi yanlıştır.1

Bizim tercihimizin gerekçesini tasarım çıkarımı olarak koyalım: HTTP sunucusu yazmak için hazır standart kütüphane, açık hata akışı ve derlenmiş programı ayrı süreç olarak çalıştırabilmek öğretici ve kullanışlıdır. Ekip başka bir dilde daha yetkin olsaydı karar değişebilirdi. Go seçmek ön yüzün TypeScript olmasını geçersiz kılmaz; ürünün farklı parçaları farklı araçlarla yapılır. Binanın elektrik tesisatını hangi aletle yaptıysan kapılarını da onunla yapmak zorunda değilsin.

36.2 Temel sözdizimi: paket, fonksiyon, değişken, struct, metot, arayüz

Önce küçük, bütün bir program. Bölüm 9'daki kaynak kod → derleyici → çalıştırılabilir dosya hattı aynı kalır; bu kez metnin yazım kuralları Go'ya aittir. Her örnek bağımsızdır: bu bölümdeki ayrı package main bloklarını aynı klasöre aynı anda koyup derleme; her birini kendi örnek klasöründe main.go olarak düşün.2

package main

import "fmt"

func main() {
    points := 40
    points = points + 10
    fmt.Println(points)
}

package main, dosyanın çalıştırılabilir programın ana paketine ait olduğunu bildirir. Paket (package), Go'da birlikte derlenen ve ad alanı paylaşan kaynak dosyalarını düzenleyen birimdir. import "fmt", metin biçimlendirme ve çıktı araçlarını kullanıma açar. func main() programın giriş fonksiyonudur. Süslü parantezler fonksiyonun gövdesini sınırlar. points := 40 yeni yerel değişken tanımlar; derleyici tipi değerden çıkarır. points = points + 10 var olan değişkene yeni değer atar. fmt.Println(points) değeri ve satır sonunu çıktıya yazar. Hesap 50'dir; programı gerçekten çalıştırınca da 50 basar.2

:= ile ='i ayır. İlki uygun yerde kısa değişken tanımıdır; ikincisi atamadır. Değişkenin ilk değerinden tipi çıkarılması dinamik tipli olması anlamına gelmez: points'e sonradan "elli" atamak tip uyuşmazlığıdır. Açık yazmak istersen var points int = 40 aynı fikri görünür kılar. Başlangıç değeri yazılmayan değişkenin tipine ait sıfır değeri (zero value) olur: tam sayıda 0, Boole'da false, karakter dizisinde boş metin. Bu, değişkenin geçmiş bellek artıklarından rastgele bir değer almasını önler.2

int tam sayı, string karakter dizisi, bool doğru/yanlış içindir. Belli bit genişliği gereken sınırda int64 gibi açık tipler seçilebilir. Ağdan gelen metin ile tam sayı arasında açık dönüştürme gerekir; bir API'nin limit parametresini “tip zaten belli” diye kabul etmeyiz. Tip derleme zamanında hangi işlemin yapılabileceğini denetler; kullanıcının gerçekten var olup olmadığını veya seçilen şıkkın geçerli aralıkta olup olmadığını denetlemek ayrıca gerekir. Bu ayrımı Bölüm 30'dan tanıyorsun.

Birlikte duran veriler: struct

Yapı (struct), adlandırılmış alanları tek değer içinde gruplar. Kullanıcı adı ile puanı birbirinden kopuk değişkenlerde taşımak yerine aynı oyuncunun alanları olarak tutarız. Metot (method) ise belirli alıcı tipe bağlanmış fonksiyondur. Aşağıdaki program ayrıca Bölüm 1'in arayüz fikrini dilin içinde gösterir.23

package main

import "fmt"

type Player struct {
    Name string
    Points int
}

func (p *Player) Add(points int) {
    p.Points = p.Points + points
}

type Scorer interface {
    Add(int)
}

func reward(s Scorer) {
    s.Add(10)
}

func main() {
    p := Player{Name: "Kerem", Points: 40}
    reward(&p)
    fmt.Println(p.Name, p.Points)
}

type Player struct yeni tipin adını ve alanlarını tanımlar. Name string ile Points int, alan adı ve tip sırasındadır. Adlarının büyük harfle başlaması onları başka paketlere açar; bu ayrıntı ileride JSON üretirken de önemlidir. func (p *Player) Add(points int) içindeki (p *Player) alıcı (receiver) tanımıdır: metot bir Player işaretçisi üzerinde çalışır. p.Points o oyuncunun alanıdır; atama asıl oyuncunun puanını değiştirir.3

İşaretçiyi Bölüm 10.3'ten tanıyorsun. Buradaki &p, değişkenin adresini verir; *Player Player'a işaretçi tipidir. Alıcıyı yalnız Player yazsaydık metot bir yapı kopyası üzerinde çalışacak, o kopyanın alanını değiştirmek çağıranın yapısını değiştirmeyecekti. Go'da parametreler değer olarak aktarılır; işaretçi geçirirken de kopyalanan şey adres değeridir. “Her şey referansla aktarılır” diye genelleme yapma.23

Arayüz: adı değil, yapabildiği iş önemli

type Scorer interface { Add(int) } bir arayüz (interface) tanımlar: buraya verilen değer Add(int) davranışını sunmalıdır. Ekran veya düğme değil, program parçaları arasındaki sözleşmedir. reward(s Scorer), somut oyuncu alanlarını bilmez; yalnız s.Add(10) çağırabileceğini bilir. *Player bu metodu sunduğu için arayüzü örtük olarak karşılar; ayrıca “Scorer'ı gerçekliyorum” diye bir anahtar kelime yazmayız.3

p := Player{Name: "Kerem", Points: 40} alanları adlarıyla doldurur. reward(&p) adresi arayüze verir; alıcı metot işaretçi üzerinde tanımlandığı için burada *Player gerekir. Son satır Kerem 50 yazar. Asıl kazanç bu çıktının kendisi değil: yarın başka bir puan nesnesi aynı davranışı sunarsa reward değişmeyebilir. Bu, gerçeklemeyi arayüzün arkasına saklamanın küçük örneğidir; Bölüm 37'de aynı fikir bellek deposunu Postgres deposundan ayıracak.

Bir metodu derleyicinin tanıması, metodun doğru iş yaptığını kanıtlamaz. Add diye adlandırıp içeride puanı azaltan kod da tipleri tutarlıysa derlenebilir. Tip kontrolü sözleşmenin biçim kısmını denetler; anlamını örneklerle ve testlerle denetlemek gerekir. Bu yüzden “derlendi, demek ki ürün doğru” sonucuna atlamıyoruz. Bölüm 42 testleri ayrı konu olarak kuracak.

36.3 Hata yönetimi: hatalar değerdir

Bir fonksiyon her zaman istediğin sonucu üretemez: dosya yoktur, bağlantı kopar, ödül tanımı geçersizdir. Go'da olağan yaklaşım sonucu ve hata bilgisini birlikte döndürmektir. Yerleşik error arayüzü, Error() string davranışını tarif eder. Hata yoksa gelen hata değeri genellikle nil'dir. nil burada hata değerinin bulunmadığını ifade eder; boş hata metniyle aynı şey değildir.3

package main

import (
    "errors"
    "fmt"
)

func award(correct bool, reward int) (int, error) {
    if reward < 0 {
        return 0, errors.New("ödül negatif olamaz")
    }
    if !correct {
        return 0, nil
    }
    return reward, nil
}

func main() {
    points, err := award(true, 10)
    if err != nil {
        fmt.Println("Hata:", err)
        return
    }
    fmt.Println("Kazanılan:", points)
    _, err = award(true, -1)
    fmt.Println("Geçersiz ödül:", err)
}

import (...) birden çok paketi alır: errors hata değeri üretmek, fmt çıktıyı yazmak içindir. award(correct bool, reward int) (int, error) iki parametre alır ve iki değer döndürür. İlk koşul negatif ödülü reddeder; errors.New açıklamalı hata üretir. return 0, ..., puan verilemediğini ve nedenini birlikte taşır. İkinci koşul cevap yanlışsa 0, nil döndürür: yanlış cevap sistem arızası değildir. Son satır, doğru cevap ve geçerli ödül için ödülü döndürür.

points, err := award(true, 10) iki dönüşü iki değişkene yerleştirir. if err != nil hata var mı diye sorar. Varsa mesajı yazıp return ile devam etmeyi bırakırız; yoksa puanı kullanırız. _, err = award(true, -1) ilk dönüşü bilinçli olarak atar, mevcut err'e yeni hata değerini yazar. Alt çizgi boş tanımlayıcı (blank identifier)dır: bu değeri kullanmayacağımızı belirtir. Bir değeri atabilmek, hataları gelişigüzel atmanın doğru olduğu anlamına gelmez.3

Kazanılan: 10
Geçersiz ödül: ödül negatif olamaz

Bu çıktı örneği gerçekten çalıştırılarak sınanmıştır. Dikkat edersen “doğru değil” ile “hesap yapılamadı” farklı kanallardadır. HTTP API'sinde bunun karşılığı da vardır: yanlış şık için normal başarı yanıtında correct:false; bozuk istek için uygun 4xx; beklenmedik iç sorun için 5xx. Dilin hata değeri ile HTTP durum kodunu otomatik eşitlemeyiz. Hangi hatanın kullanıcıya nasıl anlatılacağına HTTP katmanı karar verir.

Rob Pike'ın Errors are values yazısının ana fikri “her satırın sonuna ezberden if koy” değildir. Hata da veri olduğu için saklanabilir, bağlamla taşınabilir, iş akışına göre düzenlenebilir. Yazı, yine de hatanın sonunda kontrol edilmesi gerektiğini açıkça vurgular. Hata sonucunu yok sayarsan dosyanın yazılmadığı hâlde başarı bildirebilir veya olmayan veriyi işleyebilirsin. Açık hata yönetimi, sorumluluğu görünür kılar; sorumluluğu ortadan kaldırmaz.4

panic ve recover Go'da vardır; ama kullanıcıdan geçersiz şık gelmesini olağan dönüş değeri yerine panic ile yönetmek bu örneğin yaklaşımı değildir. Beklenen reddi normal akışta taşımak, bir istekteki sorunu bütün programın yaşamıyla karıştırmayı önler. panic'in kapsamı ve kurtarma ayrıntıları için Effective Go'nun Panic ve Recover başlıklarına dön; burada öğrenmen gereken sınır, normal iş sonucu ile işlemi tamamlayamama arasındadır.3

36.4 Goroutine ve kanal

Bölüm 14.3'te eşzamanlılığı birden çok işi ilerletebilmek, paralelliği aynı anda gerçekten yürütmek diye ayırdık. Go bu ayrımı goroutine (goroutine) ile kullanıma açar: Go çalışma zamanının yönettiği, başka işlerle eşzamanlı ilerleyen fonksiyon yürütmesidir. Bir işletim sistemi iş parçacığıyla bire bir aynı şey değildir; çalışma zamanı birçok goroutine'i işletim sistemi iş parçacıkları üzerinde düzenler.13

Başlatmak için çağrının önüne go yazılır. Bu, “hemen sonuç bende” değil, “bu yürütmeyi başlat, ben devam edeyim” demektir. Başlatan fonksiyon sonucu beklemek istiyorsa ayrıca bir eşgüdüm yolu gerekir. Kanal (channel), goroutine'ler arasında belirli tipte değer ileten ve uygun durumda onları bekleten dil aracıdır. Aşağıdaki kanal sayısal sonucu taşır; böylece “iş bitti mi” sorusuna da cevap verir.310

package main

import "fmt"

func main() {
    result := make(chan int)
    go func() {
        result <- 40 + 10
    }()
    points := <-result
    fmt.Println(points)
}

make(chan int) tam sayı taşıyan arabelleksiz kanal kurar. go func() { ... }() adı olmayan fonksiyonu yeni goroutine'de çağırır; sondaki parantezler çağrının parçasıdır. result <- 40 + 10 hesaplanan değeri gönderir. points := <-result o kanaldan değer almayı bekler. Son satır 50 yazar. Gönderim ile alımın buluşması nedeniyle main sonuç gelmeden bitmez. Burada uyumak için keyfî saniye koymadık; tamamlanmayı veriyle ilişkilendirdik.

Arabelleksiz kanalda gönderici alıcı hazır olana kadar, alıcı da gönderilecek değer gelene kadar bekler. Arabellekli kanalda belirli kapasiteye kadar değer birikebilir; dolduğunda gönderici yine bekler. Bu fark, “kanal kullandım, artık hiçbir şey beklemez” yanılgısını önler. Hiç alıcısı olmayan bir gönderim veya hiç göndericisi olmayan bir alım programı takılı bırakabilir. Eşzamanlılık yeni bir sihirli hız katmanı değil, işlerin yaşam sürelerini düzenleme sorumluluğudur.3

Goroutine'ler aynı adres alanını paylaşır. Dolayısıyla aynı haritaya veya puan değişkenine kontrolsüz yazmak, Bölüm 14.4'teki yarış durumunu doğurabilir. Kanal her paylaşılan yapıyı kendiliğinden korumaz; kilit veya başka eşgüdüm tasarımı gerekebilir. Bölüm 37'nin bellek deposunda, hem puan hem “ödül verildi mi” kaydını aynı kritik bölümde değiştireceğiz. Yalnız tek sayıya kilit koymak, iki verinin birlikte doğru kalmasını sağlamaz.1

HTTP sunucusu için bunun önemi şudur: bir istek veritabanından yanıt beklerken başka isteklerin ilerleyebilmesini isteriz. Ama “çok goroutine açmak”, veritabanının sınırsız bağlantı kabul edeceği anlamına gelmez. Alt katın kapasitesi üst katın niyetinden bağımsızdır. Bölüm 39.5 bağlantı havuzunu, Bölüm 37.4 bekleyen işin ne zaman bırakılacağını anlatacak. Şimdilik eşzamanlılığın amacı beklemeleri düzenlemek; ölçülmemiş hız vaat etmek değil.

36.5 Tek ikili dosya, standart kütüphane ve go mod

Standart kütüphane (standard library), Go dağıtımıyla gelen paketlerdir. fmt çıktı, net/http HTTP, encoding/json JSON, context iptal ve süre bağlamı sağlar. Bir HTTP sunucusunun ilk hâli için dış çerçeve kurmak zorunda olmayışımız bu araçların kapsamından gelir. Bu, dış paketlerin gereksiz veya güvensiz olduğu iddiası değildir; yalnız bir başlangıç programının hangi hazır parçalara dayandığını ayırır.35

go mod init example.com/kerem-api
go fmt ./...
go build -o api .
./api

İlk komut çalışma klasöründe go.mod kurar ve modülün adını tanımlar. Bu örnekteki alan adı yer tutucudur; komut hesap açmaz, projeyi internette yayımlamaz. İkinci komut paketlerdeki kaynak metni standart biçime getirir; ./... bu klasör ve alt paketler demektir. Üçüncü komut ana paketi ve gerekli bağımlılıkları derleyip api adlı çalıştırılabilir dosya üretir. Son satır Unix benzeri kabuklarda dosyayı başlatır; Windows'ta üretilen dosya api.exe olur ve PowerShell'de .\api.exe ile çalıştırılabilir. Bunları kitabın proje klasöründe değil, kendi deneme klasöründe düşün.56

Bölüm 27'deki “modül” fikriyle bağ kur ama dosya eşitliği varsayma. Go'da modül (module), birlikte sürümlenen paketler topluluğudur; kökündeki go.mod modül yolunu, dil sürümü gereksinimini ve bağımlılık bilgilerini taşır. Paket kodu düzenler; modül sürüm ve bağımlılık sınırını düzenler. go mod tidy kullanılan bağımlılıklarla modül kayıtlarını uyumlu hâle getirir; dış modüller gerektiğinde indirilebilir. İndirme ve değişiklik etkisini bilmeden canlı projede komut çalıştırmayız.5

go.sum, indirilen modül içeriklerinin doğrulanmasında kullanılan özet değer kayıtlarını tutar; npm'in kilit dosyasıyla birebir aynı görevi gördüğünü söylemeyiz. Bölüm 29'daki temel soru yine aynıdır: hangi dış koda, hangi sürümde güveniyoruz? go.mod ile go.sum kaynakla birlikte tutulur. Standart kütüphane dışında paket kullanmayan bu bölüm örnekleri, bir web çerçevesi indirmeden çalıştırıldı.5

Tek ikili dosya (single binary), Go programlarının dağıtımındaki sık rastlanan kolaylıktır: gerekli Go kodu ve çalışma zamanı hizmetleri bir çalıştırılabilir dosyada birleştirilebilir. Fakat “tek dosya”yı sınırsız vaat gibi okuma. Hedef işletim sistemi ve işlemci mimarisi önemlidir; Linux için derlenen dosya her Mac'te doğrudan çalışmaz. C koduyla bütünleşme olan cgo veya özel bağlama tercihleri paylaşımlı kütüphane bağımlılıkları doğurabilir. Sertifikalar, yapılandırma, statik içerik ve veritabanı da hâlâ dış ihtiyaç olabilir.16

Özellikle bizim uygulamamızda program dosyası ile kullanıcı verisini ayır. Go arka ucunu yeniden derlemek puanları içinde gömmek anlamına gelmez; asıl kalıcı veri Postgres'tedir. Programın dağıtımını sadeleştirmek veri saklama sorununu çözmez. Bölüm 38 tam bu ayrımı kuracak. Şimdilik Go dosyasının okunabilir kaynak, api'nin çalıştırılabilir çıktı olduğunu ve ikisinin de kullanıcı cevaplarının kalıcı deposu olmadığını bil.

36.6 Go ile Node, Python, Java: hangi durumda hangisi?

Önce adları aynı hizaya koy: Go, Python ve Java dil adlarıdır; Node.js JavaScript'i tarayıcı dışında çalıştıran çalışma ortamıdır. “Go mu Node mu?” günlük konuşmada anlamlıdır ama karşılaştırılan şey dil + çalışma ortamı + kütüphaneler bütünüdür. Dil kartlarını Bölüm 21.6'da gördün. Burada karar tablosunu bizim arka ucun sorularına göre daraltıyoruz.

AdayNasıl bir yaklaşım?Değerlendirilecek bedel
GoStatik tipler, yerel derleme, goroutine ve açık hata dönüşleriÖn yüz dışında ikinci dil; veri ve hata dönüştürmelerini yazma yükü
Node.js + TypeScriptÖn yüzle aynı dil ailesi; olay döngüsü ve eşzamansız giriş/çıkışUzun süren JavaScript işi olay döngüsünü tutabilir; çalışma sınırlarını düzenlemek gerekir
PythonDinamik tipler, okunabilir sözdizimi; betik ve hızlı deneme için elverişli araçlarÇalışma anı doğruluğunu ve dağıtım ortamını ayrıca tasarlamak gerekir
JavaJVM ortamı; platform iş parçacıkları yanında sanal iş parçacıkları da vardırÇalışma ortamı ve ekipteki alışkanlıklarla birlikte değerlendirilir

Node'un resmî belgesi, varsayılan tek JavaScript iş parçacığına rağmen engellemesiz giriş/çıkışı olay döngüsü ve alttaki işletim sistemiyle açıklar. Dolayısıyla “Node yalnız tek istek işler” yanlıştır; bekleme ile işlemciyi uzun süre meşgul etmeyi ayırmak gerekir. Python öğreticisi dilin dinamik tiplerini, yorumlanan doğasını ve betik/hızlı geliştirme kullanımını vurgular. Java belgeleri de sanal iş parçacıklarını açıkça anlatır: “hafif eşzamanlılık yalnız Go'da var” diye bir üstünlük kuramayız.789

Uygulamamızda: tercih bir iş bölümü kararıdır

Planımız Expo + TypeScript ile ekranlar, TanStack Query ile sunucu durumunun istemci kopyası, Go ile API, Postgres ile kalıcı kayıttır. Prototipinin vanilla HTML/CSS/JS dosyalarını görmedik; hangi fonksiyonun taşınacağını uydurmuyoruz. Ama güven sınırının hangi tür kararı taşıdığını biliyoruz: puan, seri ve can kuralları sunucuda doğrulanmalı. Go seçimi bu kuralları kendiliğinden doğru yapmaz. Agent'a “Go ile yaz” demenin yanına “puanı istemciden alma, tekrar teslimi belirle, hatayı kontrol et” koşullarını koymalısın.

Bu tercih için “Go en hızlı” veya “Python ölçeklenmez” gibi sloganlar kullanmayacağız. Aynı ürün farklı dillerle doğru kurulabilir. Bazen tek dil kullanan ekip Node'la daha az zihinsel yük taşır; veri/deney ağırlıklı görevde Python araçları belirleyicidir; mevcut Java ekosistemine bağlı kurum Java'yı tercih edebilir. Bunlar bizim karar çerçevemizdir, benchmark sonucu değil. Bizim kitapta Go'nun seçimi sabit; amacı seçimin gerekçesini okuyabilmen, başka araçları küçümsemen değil.

Bölüm 9 ve 14 ile bağlantı

Bölüm 9'daki derleme hattına çalışma zamanı hizmetleri ve paket düzeni eklendi. Bölüm 14'te işletim sisteminin işleri nasıl paylaştırdığını gördün; şimdi dilin goroutine ve kanalla aynı sorunun üst düzeyini sunduğunu görüyorsun. Kat 5'teki araç, Kat 7'nin iş parçacıklarını kullanıyor; sonuçta Kat 10'daki API birden çok isteği yönetebiliyor. Kat planını kaybetmedik, yalnız araç kutusunun kapağını açtık.

Bu bölümde neler kuruldu

Go statik tipli ve derlenen bir dildir; çalışma zamanı hizmetleri vardır. Paket kodu, modül bağımlılık ve sürüm sınırını düzenler. Yapı verileri toplar; metot alıcı üzerinde çalışır; arayüz gerekli davranışı tarif eder. Hata bir değerdir, yanlış cevap ile sistem arızasını ayırmayı sağlar. Goroutine işleri eşzamanlı ilerletir; kanal iletişim kurar, yarış durumunu tasarlamak yine bize kalır. Bir sonraki bölüm bu araçları HTTP sözleşmesine bağlar.

Terimler sözlükte, Bölüm 36 başlığı altında.

Birincil okuma

İlk okuma: kaynak 1, Go FAQ What is the purpose of the project?, Why did you create a new language? ve Why goroutines instead of threads?. Derinleşme: kaynak 3, Effective Go Methods ve Interfaces and other types; kaynak 4, Errors are values.

Takıldığın yeri yapay zekâ öğretmenine sor. Bu bölümün alıştırması: Player örneğinde işaretçi alıcısını değer alıcısına çevir, sonucu önce tahmin et, sonra ayrı deneme klasöründe çalıştır ve farkı anlat.

Bölüm sonu soruları

Kitaba bakmadan cevapla. Geri bildirim yalnız sonucu değil, gerekçeyi de gösterir.

1. Go için hangi ifade doğrudur?
Go çalışma zamanı çöp toplama ve goroutine yönetimi gibi hizmetler sunar. Bkz. 36.1.
2. points := 40 satırında ne olur?
Kısa değişken tanımı, tipi başlangıç değerinden çıkarır; değişken dinamik tipli olmaz. Bkz. 36.2.
3. Arayüzü karşılamak neye bağlıdır?
Go arayüzü örtük karşılanır; gereken metot kümesi önemlidir. Bkz. 36.2.
4. Yanlış cevap için 0, nil dönmesi neyi anlatır?
Yanlış şık normal iş sonucudur; hata değeri üretmek zorunda değildir. Bkz. 36.3.
5. Goroutine ile işletim sistemi iş parçacığı ilişkisi nedir?
Go çalışma zamanı goroutine yürütmelerini işletim sistemi iş parçacıklarına paylaştırır. Bkz. 36.4.
6. Arabelleksiz kanalda sonuç almak ne sağlayabilir?
Alım sonuç gelene kadar bekler; bu örnekte main erken bitmez. Bkz. 36.4.
7. Go modülünün temel rolü nedir?
Modül, paketleri birlikte sürümleme ve bağımlılık yönetimi sınırında toplar. Bkz. 36.5.
8. Dil seçimini değerlendirirken hangi yaklaşım uygundur?
Araç seçimi gereksinim ve ekip bağlamıyla yapılır; mutlak hız sıralaması değildir. Bkz. 36.6.
9. Player.Add metodunun alıcısını *Player yerine Player yaparsan neden çağıranın puanı değişmez?
Örnek cevabı göster

Yapı değeri kopyalanır; metot alanı kopyada değiştirir. İşaretçi alıcısı asıl yapıya ulaşmayı sağlar; geçirilen işaretçi de bir değer olarak kopyalanır. Bkz. 36.2.

10. Bir agent “Go goroutine kullandığı için yarış durumu olmaz” diyor. Nasıl cevap verirsin?
Örnek cevabı göster

Goroutine aynı adres alanındaki işleri eşzamanlı ilerletir. Ortak yazma varsa kilit, kanal veya başka eşgüdüm gerekir. Dilin işi başlatmayı kolaylaştırması veri tutarlılığını otomatik sağlamaz. Bkz. 36.4.

Kaynaklar

  1. Go FAQ · Origins; Design; Concurrency: tarih, tasarım hedefleri, çalışma zamanı ve goroutine.
  2. The Go Programming Language Specification: Declarations and scope; Struct types; Method sets; Interface types; Calls; Zero value.
  3. Effective Go · Methods; Interfaces; Errors; Concurrency: alıcılar, örtük sözleşme, hata ve kanal kullanımı; bu belge modüllerin güncel rehberi değildir.
  4. Rob Pike · Errors are values: hata değerleriyle açık iş akışı.
  5. Go · Managing dependencies: go.mod, go.sum, go mod init ve tidy.
  6. Go command documentation · Compile packages and dependencies: go build çıktısı ve derleme seçenekleri.
  7. Node.js · The Node.js Event Loop: varsayılan JavaScript yürütmesi ve engellemesiz giriş/çıkış.
  8. Python · The Python Tutorial: dinamik tipler, yorumlanan doğa, betik ve hızlı geliştirme.
  9. Oracle · Virtual Threads: platform ve sanal iş parçacıkları ayrımı.
  10. A Tour of Go · Concurrency: Goroutines; Channels; sync.Mutex: yazılı tur; resmî website deposundaki aynı ders metniyle de karşılaştırıldı.