Concurrency

Concurrency, 'eş zamanlı' anlamına gelir. İsterseniz devamını okumadan önce bir beyin fırtınası yapıp bunun ne için kullanılabileceğini düşünün.

Bazen birkaç işi aynı anda yapmak zorunda kalabiliriz. Örneğin yürüyüş yaparken telefonda acilen halletmeniz gereken bir iş çıktığını varsayın. Bu durumda yürürken aynı zamanda o işi de yaparsınız, öyle değil mi? İşte, biz bu yöntemi bir fonksiyonun geç çalışması durumunda kullanıyoruz. Yani yürüyüştesiniz; asıl hedefiniz yürümektir ama acil bir iş çıktı ve telefondan onu halletmek zorundasınızdır. Aynen bu mantıkla düşünebilirsiniz. Bunları, Go ile bağdaştıracak olursak örneklerle ilerlemek daha sağlıklı olacaktır.

[*] Ekran kocaman olduğu için özellikle telefondan okuma zorluğu yaşayabilirsiniz (yakınlaştırarak okumayı deneyin). Böyle bir durumla karşılaşırsanız lütfen geri bildirim sağlayın; düzeltmek için çalışırız.

Kırmızı kutulara bakın; iki adet fonksiyon yazmışız ve printC() fonksiyonunun amacı 1000 defa 'C' yazdırmak; printW() fonksiyonunun amacı ise 300 defa 'W' yazdırmak. Normal şartlarda main fonksiyonuna tanıttığımız fonksiyonlar sırayla çalışır, değil mi? Zaten yukarıdaki ekran görüntüsünde de durum böyle. İlk önce 'C'leri, daha sonra 'W'ları yazmış.

Bu fonksiyonların daha ağır işler yaptığını ve bu yüzden diğer fonksiyonun çalışmasının geciktiğini düşünün. Bir web sitesinde, bir bankada veya bir şirkette böyle bir durumla karşılaşmak istemeyiz. Bu yüzden bir fonksiyonun çalışması gecikecekse, diğer fonksiyon iş yapsın ki gecikecek olan fonksiyon çalışana kadar bari diğer işleri halletmiş olalım. Mantık bu şekildedir.

İşte biz, bu iki işlemin aynı anda yapılmasını istiyoruz. Bunun için GoLang'de 'goroutine' dediğimiz bir yapı var. Türkçesi 'go rutini'dir. İsminden de anlayacağınız üzere bunları, bir rutinmiş gibi; yapılması gereken işler olarak kullanıyoruz ve gerektiğinde biri bitmeden diğerini de yapabilmek istiyoruz. Bu rutini oluşturmak için fonksiyonun başına 'go' anahtar kelimesini girmemiz yeterli olacaktır.

C fonksiyonu için işini biraz daha zorlaştırıp 1000 olan değeri 15000'e çıkardım. Daha sonra 'go printC()' ve 'go printW()' şeklinde tanımlamalar yaptım. Ee, neden çalışmadı; sorun ne?

Bir GoLang dosyası içerisindeki main() fonksiyonu başlı başına bir rutin yani 'go routine'dir. main() zaten kendi içerisindeki işlemleri bir rutin olarak gerçekleştireceğini bilir fakat biz içerideki fonksiyonlara da 'rutin' ataması yaptığımız için main() fonksiyonu onları kendi hallerine bıraktı ve o fonksiyonlar henüz çalışmadan program sonlandı. Programın kapanması biraz daha sürseydi bu fonksiyonlar da çalışacaktı ama main() daha hızlı çalıştı. Bu sorunu nasıl çözebiliriz?

Bu sorunu, programı biraz bekleterek halledebiliriz. Bunun için de 'time' adında br kütüphane kullanmamız sağlıklı olacaktır. Bu kütüphane bize, zamana dayalı işlemleri yapmamıza olanak tanır. Ben burada sadece kullanacağımız metodu göstereceğim fakat daha derine bakmak sizin göreviniz.

Önce 'import' kısmına 'time' kütüphanesini ekledim. Daha sonra fonksiyonlardan sonra programı beklettim ('time.Sleep(2 * time.Second)'). Bu sayede kodum gayet çalıştı ve ilk seferde olacağını hiç düşünmediğim bir çıktı geldi; çok mutluyum :).

Çıktıya bakalım. Az önce, farklı işleri aynı anda yapmaktan bahsettik. Çıktıya baktığımızda, W ve C harfleri karışık şekilde işlenmiş ve gösterilmiş olduğunu görüyoruz. İşte, eş zamanlılık tam olarak budur. Hatta çıktı üzerinden gerçek hayattan bir örnek verelim.

W harfi menemen olsun, C harfi de sofraya çeşitli kahvaltılıkların eklenmesi/dizilmesi olsun. Hangisinin daha uzun süreceğini bilemeyiz fakat biz, sofraya kahvaltılık ekleme işinin daha uzun süreceğini varsayalım. Çıktının ilk adımlarına bakalım; W. Biz bu sırada menemen için yağı ekleyip gerekli malzemeleri doğruyor olalım ve doğradıktan sonra tavaya atalım. Şimdi C harfleri başladı; malzemeler pişerken biz de kahvaltılıkları dizmeye başladık. Daha sonra tekrar W harfleri gelmeye başladı. Bu durumda malzemelerin piştiğini anladık ve geri dönüp tavaya çırpılmış yumurtaları döktük. Daha sonra tekrar C harfleri gelmeye başladı ve biz yumurtanın olmasını beklerken kalan kahvaltılıkları sofraya dizmeye devam ettik. Bir süre sonra yine W harfleri gelmeye başladı. Bu durumda biz, menemenin altını kapattık (bu sırada tekrar C harfleri başladı) ve onu da sofraya eklemeye gittik. İşte, GoLang'de eş zamanlılık budur :).

Peki, her koşulda ilk önce C harflerinin yazdırılmasını yani printC() fonksiyonun çalışmasını ve tamamen bitmesini bekledikten sonra diğerlerine geçmek istersek bunu nasıl yapabiliriz?

Bunun için sync adında bir modül ve bu modül içerisinde WaitGroup adında bir metodumuz var. Bu metot içerisinde de önemli olan 'Add', 'Wait' ve 'Done' yani 'ekle', 'bekle' ve 'bitir' şeklinde üç metodumuz daha var. Hemen bakalım.

Öncelikle 'import' kısmına 'sync' kütüphanesini ekledik. Sonra bu modül içerisindeki fonksiyonu global alana 'var wg sync.WaitGroup' şeklinde bir değişkene atadık. Buradaki wg değişkenimizdir ve WaitGroup'un kısaltmasıdır.

Dikkat ederseniz main() içerisinde az önce iki tane rutinimiz vardı ama şimdi birini kaldırdım. Eğer sync modülünün yardımı olmasaydı yani sade bir şekilde sadece bir tane rutin olsaydı Go, rutini değil normal fonksiyonu çalıştıracaktı yani sadece W harfini görecektik (deneyin). Bu yüzden wg.Add(1) şeklinde bir kod yazdık ve main() fonksiyonuna; "Senden sonra gelen bir tane rutin var" şeklinde bir uyarı yapmış olduk ve go printC() şeklinde rutinimizi sağladık.

Daha sonra wg.Wait() kodu ile diğer fonksiyona geçmesini engelledik; "Rutinin bitmesini bekle" şeklinde bir uyarı verdik.

Son olarak rutine ait olan fonksiyona gittik (printC()) ve ona, wg.Done() dedik. Bunun anlamı; "İşin bitince main()'e haber ver ki diğer işlevler çalışsın" şeklinde ifade edilebilir. İsterseniz şimdi çıktıya bakalım.

Evet, gördüğünüz gibi ne olursa olsun; ne kadar yoğun olursa olsun ilk önce C harflerini verdi ve işi bittikten sonra W harflerini vermeye başladı.


Yayınlanma Tarihi: 2022-08-18 23:10:39

Son Düzenleme Tarihi: 2022-08-20 22:29:48