67 / 73

Race conditions

Dasturchilar ko'pincha Data Race va Race Condition atamalarini bitta narsa deb adashtiradilar. Aslida esa ular tubdan farq qiladi: dasturingizda hech qanday ma'lumotlar poygasi (data race) bo'lmasligi, lekin shunga qaramay jiddiy mantiqiy poyga (race condition) tufayli noto'g'ri ishlashi mumkin!

Bob mundarijasi:


Data race va Race condition farqi

  • Data Race (Ma'lumotlar poygasi): Ikkita goroutine bir vaqtning o'zida bitta xotira katagiga sinxronizatsiyasiz murojaat qilishi va ulardan kamida biri yozishi. Bu Go darajasida go run -race orqali aniqlanadi.
  • Race Condition (Mantiqiy poyga holati): Dasturning natijasi goroutine'larning qaysi biri oldinroq bajarilishiga bog'liq bo'lib qolishi (mantiqiy xato). Qiziq jihati shundaki, Race condition bo'lgan kodda barcha o'zgaruvchilar Mutex bilan o'ralgan bo'lishi va Go Race Detector hech qanday xato topmasligi mumkin!

Check-then-act muammosi

Keling, bank hisobidan pul yechish misolini ko'raylik:

example.go
type Account struct {
	mu      sync.Mutex
	balance int
}

func (a *Account) Balance() int {
	a.mu.Lock()
	defer a.mu.Unlock()
	return a.balance
}

func (a *Account) Withdraw(amount int) {
	a.mu.Lock()
	defer a.mu.Unlock()
	a.balance -= amount
}

Bu yerda har bir metod alohida Mutex bilan himoyalangan, shuning uchun Data Race yo'q.

Endi quyidagi kodni ko'ring:

example.go
// Agar pul yetarli bo'lsa, pul yechilsin:
if account.Balance() >= 100 { // (1) Check (Tekshirish)
	// Aynan shu yerda boshqa goroutine pulni yechib ketishi mumkin!
	account.Withdraw(100)        // (2) Act (Amalni bajarish)
}

Mana bu haqiqiy **Race Condition (Check-then-act)**dir:

  1. Goroutine A balansni tekshiradi (100 bor, yetarli).
  2. Aynan shu millisekundda Goroutine B ham balansni tekshiradi (100 bor, yetarli).
  3. Goroutine A pulni yechadi (balans 0 bo'ldi).
  4. Goroutine B ham pulni yechadi va balans minusga (-100) tushib ketadi!

Yechim: Tekshirish va yechish amallari bitta atomik metod ichida, yagona qulf ostida birlashtirilishi kerak:

example.go
func (a *Account) TryWithdraw(amount int) bool {
	a.mu.Lock()
	defer a.mu.Unlock()

	if a.balance >= amount {
		a.balance -= amount
		return true // muvaffaqiyatli
	}
	return false // mablag' yetarli emas
}

TryLock metodining xavfi

Go 1.18 dan boshlab sync.Mutexga TryLock() metodi qo'shildi. U qulf bo'sh bo'lsa darhol egallab true qaytaradi, aks holda kutmasdan false beradi.

[!WARNING] TryLockni sikl ichida kutish (busy loop) uchun ishlatmang! Bu protsessorni 100% yuklab qo'yadi. TryLock faqatgina "agar hozir qulf bo'sh bo'lsa amalni bajar, band bo'lsa keyinroqqa qoldir yoki umuman bajarma" degan juda kam uchraydigan holatlar uchun mo'ljallangan.


Shared Nothing falsafasi

Go tilining shiori:

"Do not communicate by sharing memory; instead, share memory by communicating." (Xotirani ulashish orqali aloqa qilmang; aloqa qilish orqali xotirani ulashing).

Eng xavfsiz concurrency — bu bir nechta goroutine'lar o'rtasida umumiy o'zgaruvchilar bo'lmasligi (Shared Nothing). Har bir goroutine o'z ma'lumotlariga mustaqil egalik qilsin va faqat kanallar orqali tayyor natijalarni uzatsin.


Topshiriq: Xavfsiz hisobdan pul yechish

Yuqoridagi TryWithdraw andozasidan foydalanib, balans minusga tushib ketmasligini kafolatlovchi xavfsiz bank hisobini tuzing.

example.go
package main

import (
	"fmt"
	"sync"
)

type BankAccount struct {
	mu      sync.Mutex
	balance int
}

func (b *BankAccount) Withdraw(amount int) bool {
	// TODO: Balans yetarli bo'lsa yechib true qaytaring, aks holda false
	return false
}

func main() {
	acc := &BankAccount{balance: 100}
	var wg sync.WaitGroup

	// Bir vaqtda ikkita ishchi 100 so'm yechishga urinadi:
	for i := 0; i < 2; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			if acc.Withdraw(100) {
				fmt.Printf("Mijoz #%d: 100 so'm yechildi!
", id)
			} else {
				fmt.Printf("Mijoz #%d: Mablag' yetarli emas!
", id)
			}
		}(i)
	}

	wg.Wait()
	fmt.Println("Yakuniy balans:", acc.balance)
}

Xulosa va keyingi qadamlar

Sinxronizatsiyada faqat Data Race'dan emas, balki mantiqiy Race Condition'dan ham ehtiyot bo'lish zarur.

Keyingi mavzu: Semaphores (Semaforlar va Rendezvous andozasi)!