Golang için Race Condition ve Mutex Kullanımı

Ömer Burak Demirpolat
2 min readNov 22, 2020

--

Selam, belirtilen tanımlar ile başlayıp bir örnek ile Race Condition durumunu özetleyecek yine aynı örnek içerisinde Mutex kullanarak Race Condition durumuna önlem alacağım.

Race Condition

Bu kavramın tanımı, ortak kullanılan bir bellek adresi/adreslerinin 2 veya daha fazla thread’in (go için goroutine) bu adreste bulunan veriyi değiştirmesi sonucunda oluşan durumdur.

Şöyle bir senaryo ile özetleyeyim;

Bir blog sitemiz var ve bloglardan herhangi birine her bir tıklama yapıldığında, GET istek ulaştığında veri tabanındaki “ziyaretçi” veya “okunma sayısı” gibi bir değer arttırılıyor.

Bir yazılım sürecinde veri tabanındaki değerini arttırıp güncellemek için ne yaparsınız? Önce SELECT sorgusu ile değere ulaşır ve sonrasında bu değeri +1 yaparak UPDATE sorgusu gönderiyoruz.

Buradaki potansiyel sorun ise şu;

Yazının veri tabanındaki güncel “okunma sayısı” değeri 213.

Bu esnada iki adet ziyaretçi aynı anda makaleyi okumak üzere siteye giriş yaptı.

İstekler aynı anda geldiği için veri tabanından değeri istediğimizde iki ziyaretçinin bulunduğu thread’lere de 213 olan “okunma sayısı” değerini gönderdi.

Yazılımızın 2 thread’i de bu değeri 214 olarak 1 adet arttırarak veri tabanına kaydetmek üzere UPDATE sorgusunu iletti.

2 ziyaretçi siteye bağlanmadan önce 213 olan okunma sayısı 2 ziyaretçinin ardından 215 olması gerekirken 214 oldu. Bu bir bug, hata. Artık adına ne derseniz.

Bu sorunu çözmek için veri tabanlarında transaction’lar kullanılmaktadır.

Ben Go için örnek yaparken veri tabanı üzerinden değil constant’lar kullanarak gerçekleştireceğim.

Aşağıdaki kodun nasıl bir çıktı vermesini beklersiniz?

Komut satırında “1000” mi yazar?

for i := 0; i < 1000; i++ {    go func() {      doneCount++    }()  }time.Sleep(time.Second * 2)fmt.Println(doneCount)

Bu kodun çıktısı her defasında farklı olacaktır. Bazen “950” bazen “960” çünkü goroutine’ler eşzamanlı çalışıyor ve aynı anda bellekteki doneCount adresine erişen goroutine’ler başka bir goroutine’in yazma işlemi henüz bitmeden üstüne yazmış olabiliyor.

Aslında goroutine’in bir değişkene ulaşması da veri tabanındaki gerçekleşen işlemdem çok farklı değil. goroutine bellekten doneCount değerini istiyor ve ++ ile değerini arttırıp tekrar belleğe yazılmak üzere gönderiyor.

Race condition bana göre bu şekilde özetlenebilir. Şimdi bu durumu nasıl önleyeceğimize bakalım.

Mutual Exclusion, Go için Mutex

Mutex, birden fazla thread/goroutine tarafından okuma, yazma yapılabilen bir veriye sadece bir thread/goroutine’inin erişim sağlaması için hazırlanmış, aynı anda iki adet thread/goroutine’in erişmesini önleyen, sıraya sokan yapıdır. Mutex sadece Go’da değil bir çok sistemde Mutual Exclusion adı ile mevcut bir yapıdır. Mutex bu işlemi Lock() ve Unlock() adındaki 2 fonksiyonu ile gerçekleştiriyor.

Adından da anlaşılacağı üzere Lock() kilitliyor ve başka goroutine’lerin erişmesini engelliyor ve sadece kilitleme işleminin yapıldığı goroutine’in işlem yapabilmesine imkan tanıyor. Unlock() işlemi ise bu kilidi kaldırıyor.

Kodumuzda uygulayalım ve sonucu görelim.

mutex := sync.Mutex{}for i := 0; i < 1000; i++ {  go func() {    mutex.Lock()    doneCount++    mutex.Unlock()   }()  }time.Sleep(time.Second * 2)fmt.Println(doneCount)

Bu kod ise terminalimize her defasında “1000” yazacaktır.

Bitti, tşkkrlr.

--

--

No responses yet