CheckWebsites
isminde bir fonksiyon yazar.true
, başarısız cevaplar için false
.WebsiteChecker
'a parametre göndermelisiniz. Bu, bütün web sitelerini kontrol etmek için kullanılacak.CheckWebsites
'ın hızını test etmek için benchmark kullanalım bu sayede yaptığımız değişikliklerin etkilerini görürüzWebsiteChecker
'ın sahte bir implementasyonunu kullanarak CheckWebsite
'ı test eder. slotStubWebsiteChecker
kasten yavaş. Tam olarak yirmi milisaniye beklemek için time.Sleep
kullanır ve sonra true döner.go test -bench=.
(veya Windows Powershell'de iseniz go test -bench="."
) kullanarak benchmark çalıştırdığımızda:CheckWebsites
, 2249228637 nanosaniye olarak ölçüldü - yaklaşık iki saniye.CheckWebsites
'ı nasıl daha hızlı yapacağımızı anlayabilirsiniz. Sıradaki web sitesine istek atmadan önce web sitesinden cevap beklemek yerine, bilgisayarımıza beklerken sıradaki isteği atmasını söyleyeceğiz.doSomething()
fonksiyonunu çağırdığımızda dönüş yapması için bekleriz (dönecek bir değeri olmasa bile yine de bitmesi için bekleriz). Bu işleme blocking işlemi diyoruz - Bitmesi için bizi bekletiyor. Go'da blocklamayan operasyon, goroutine olarak isimlendirilen ayrı bir process'te çalışır. Process'i, Go kod sayfasının yukarıdan aşağıya okunmasi gibi düşünün, fonksiyonun ne yaptığını okumak için her bir fonksiyonun 'içine' gitmek gibi düşünün. Ayrı bir process başladığında, başka bir okuyucu fonksiyonun içini okurken orjinal okuyucu sayfanın aşağısında gitmeye devam etmesine benziyor.go
keywordu koyuyoruz: go doSomething()
go
koymak, goroutine başlatmak istediğimizde genellikle anonymus fonksiyonları kullanıyoruz. anonymus fonksiyon literali, normal fonksiyon tanımı ile aynı, sadece isimsiz(şaşırtmayacak şekilde). Yukarıda for
döngüsünün içinde görebilirsiniz.()
işareti bunu yapmakta. İkinci olarak, tanımlandıkları lexical scope'a erişim sağlarlar - anonymus fonksiyonu tanımladığınuz noktada mevcut olan tüm değişkenler, aynı zamanda fonksiyonun body'sinden de erişilebilirlerWebsiteChecker
fonksiyonu), her birinin sonucunu result map'e ekleyen yeni bir goroutine başlatması.go test
çalıştırdığımızda:CheckWebsites
şimdi boş bir harita döndürüyor. Ne yanlış gitmiş olabilir?for
loopumuz başladığında, goroutinelerin hiçibiri sonuçlarını results
map'e ekleyecek kadar zaman bulamıyor; WebsiteChecker
fonksiyonu onlar için çok hızlı, hala boş map dönüyor.url
değişkeni for
loop'un her iterasyonunda tekrar tekrar kullanılmakta - her defasında urls
'ten yeni bir değer alıyor. Ancak, her goroutine url
değişkeninin referansına sahip - Kendi bağımsız koyalarına sahipr değiller. Böylece, hepsi iterasyonun sonundaki url
'in sahip olduğu değeri yazdırıyor - son url. Bu yüzden elimizdeki tek sonuç son url.u
- ve ardından url
parametresi ile anonymus fonksiyonu çağırarak, goroutini başlattığımız loop'un iterasyonu için u
değerinin url
değeri olarak sabitlendiğinden emin oluruz. u
, url
değerinin kopyası, bu sayede değiştirilemez.fatal error: concurrent map writes
. Bazen, testlerimizi çalıştırdığımızda, goroutinelerin ikisi results map'e gerçkten de aynı anda yazar. Go'da mapler, aynı anda birden fazla şeyin kendilerine yazmaya çalışmasını sevmez, sonuç olarak fatal error
.race
flagi ile çalıştırın: go test -race
WARNING: DATA RACE
oldukça açık. Hatanın gövdesini okurken, bir harita üzerinde yazma gerçekleştiren iki farklı goroutin görebiliriz:Write at 0x00c420084d20 by goroutine 8:
Previous write at 0x00c420084d20 by goroutine 7:
/Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:12
/Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:11
WebsiteChecker
fonksiyonunu çalıştırma işini yapan, her bir goroutine'i düşünmek istiyorum.results
map'in yanında artık resultChannel
var, aynı make
ile oluşturduğumz gibi. chan result
channel tipi -result
channel'ı. Yeni tip, result
, WebsiteChecker
'ın dönüş değerini kontrol edilen url ile ilişkilendirmek için yapıldıstring
ve bool
yapısıdır. Adlandırılacak her iki değere de ihtiyacımız olmadığı için, her bir yapı içinde anonimdir; Değerlerin isimlendirmek zor olduğunda oldukça kullanışlı olabiliyor.map
'e doğrudan yazmak yerine, wc
'ye yapılan her çağrıyı result
yapısını resultChannel
'a send statement ile gönderiyoruz. Bu <-
operatörünü kullanır, sol tarafına channel ve sağ tarafına değeri alır:for
loop, her bir url'i iterate eder. İçerisinde, değeri bir channel'dan alan ve bir değişkene atayan , receive expression kullanıyoruz. Bu da <-
operatörünü kullanır ama iki operand yer değiştirdi: channel sağda ve atayacağımız deişken de solda:result
'ı kullanıyoruz.wc
çağrılarının her biri ve result channel'ına gönderilen her bir çağrı kendi processi içinde paralel olarak gerçekleşse de, sonuç kanalından receive expression ile değerleri birer birer çıkarıyoruz.CheckWebsites
fonksiyonunun uzun bir yeniden düzenlemesinde yer alıyoruz; girdiler ve çıktılar asla değişmedi, sadece daha hızlı oldu. Ama yazdığımız testler, yazdığımız benchmark yanı sıra, CheckWebsites
'ı hala çalıtşığına dair güveni sağlayacak şekilde refactor etmemizi sağladı, aslında daha hızlı olduğunu gösteriyor.