GoDasturchi
The Race Hiding in Two Goroutines

The Race Hiding in Two Goroutines

Ikki goroutine ichida yashiringan race

Bitta katta doskani tasavvur qiling: ikki odam BIR VAQTNING o'zida, doskaning BIR XIL joyiga, boshqa-boshqa raqamlarni yozmoqchi. Agar ular navbat bilan yozmasa, natija — ikkala raqamning chala-chatti aralashmasi, hech biri to'liq o'qib bo'lmaydigan holda. Go dasturida ikkita goroutine bir xil xotiraga NAZORATSIZ yozganda ham AYNAN shunday "aralashib ketish" — race condition — yuz beradi.

Quyidagi kod review'ga yuborilgan: "Buyurtmalar ro'yxatini PARALLEL qayta ishlab, umumiy summani hisoblaydi — tezlik uchun har bir buyurtma alohida goroutine'da ishlanadi."

orders.go
type OrderTotal struct {
	Sum int
}

func (t *OrderTotal) Add(amount int) {
	t.Sum += amount
}

func ProcessOrders(orders []Order) int {
	total := &OrderTotal{}
	var wg sync.WaitGroup

	for _, o := range orders {
		wg.Add(1)
		go func(o Order) {
			defer wg.Done()
			validated := validate(o)
			total.Add(validated.Amount)
		}(o)
	}

	wg.Wait()
	return total.Sum
}

Bu kod ko'rinishidan to'g'ri — sync.WaitGroup to'g'ri ishlatilgan, wg.Wait() barcha goroutine tugashini kutadi. Lekin Concurrency Fundamentals kursida ko'rgan muhim tafsilot yetishmayapti: t.Sum += amount qatori — O'QISH va YOZISHNI ikkita alohida amal sifatida bajaradi (avval eski qiymatni o'qiydi, keyin yangisini yozadi). Agar ikkita goroutine bu qatorni AYNAN bir vaqtda bajarsa, ikkalasi ham BIR XIL eski qiymatni o'qib, ikkalasi ham o'zining qo'shimchasini qo'shib yozadi — natijada BITTA qo'shish YO'QOLIB ketadi.

  • Bu xato HAR SAFAR ko'rinmaydi — kichik ro'yxatlarda yoki sekin mashinada goroutine'lar kamdan-kam to'qnashadi, shuning uchun test ko'pincha "o'tadi"
  • Xato faqat KATTA ro'yxatlarda, KUCHLI YUKLAMADA yoki go test -race bayrog'i bilan ishga tushirilganda aniq ko'rinadi — bu Systematic Debugging kursidagi "faqat kuchli yuklamada sodir bo'ladigan xato" belgisi
  • To'g'ri kod: OrderTotal struct'iga sync.Mutex qo'shib, Add metodida t.mu.Lock(); defer t.mu.Unlock() bilan yozishni himoyalash, YOKI har bir goroutine natijasini kanal orqali yuborib, BITTA joyda (asosiy goroutine'da) yig'ish

Review'da race condition'ni topishning eng ishonchli usuli — kodni o'qib "bu yerda bir nechta goroutine BIR XIL o'zgaruvchiga yozyaptimi" deb so'rash, va agar javob "ha" bo'lsa, mutex yoki kanal himoyasi BORLIGINI tekshirish. Bundan tashqari, real loyihalarda go test -race bayrog'ini CI (avtomatik tekshiruv) tizimiga qo'shish — bu turdagi xatoni inson ko'zidan qochib ketishidan oldin avtomatik ushlaydi.

Key Takeaway

Key Takeaway:

Bir nechta goroutine bir xil o'zgaruvchiga mutex yoki kanalsiz yozsa — bu race condition, hatto sync.WaitGroup to'g'ri ishlatilgan bo'lsa ham (WaitGroup faqat KUTISHNI, YOZISHNI HIMOYALASHNI emas, ta'minlaydi). Bu xato kamdan-kam, faqat kuchli yuklamada ko'rinadi.

NEXT UP

The Resource That Never Closes

The Race Hiding in Two Goroutines

Ikki goroutine ichida yashiringan race

Bitta katta doskani tasavvur qiling: ikki odam BIR VAQTNING o'zida, doskaning BIR XIL joyiga, boshqa-boshqa raqamlarni yozmoqchi. Agar ular navbat bilan yozmasa, natija — ikkala raqamning chala-chatti aralashmasi, hech biri to'liq o'qib bo'lmaydigan holda. Go dasturida ikkita goroutine bir xil xotiraga NAZORATSIZ yozganda ham AYNAN shunday "aralashib ketish" — race condition — yuz beradi.

Quyidagi kod review'ga yuborilgan: "Buyurtmalar ro'yxatini PARALLEL qayta ishlab, umumiy summani hisoblaydi — tezlik uchun har bir buyurtma alohida goroutine'da ishlanadi."

orders.go
type OrderTotal struct {
	Sum int
}

func (t *OrderTotal) Add(amount int) {
	t.Sum += amount
}

func ProcessOrders(orders []Order) int {
	total := &OrderTotal{}
	var wg sync.WaitGroup

	for _, o := range orders {
		wg.Add(1)
		go func(o Order) {
			defer wg.Done()
			validated := validate(o)
			total.Add(validated.Amount)
		}(o)
	}

	wg.Wait()
	return total.Sum
}

Bu kod ko'rinishidan to'g'ri — sync.WaitGroup to'g'ri ishlatilgan, wg.Wait() barcha goroutine tugashini kutadi. Lekin Concurrency Fundamentals kursida ko'rgan muhim tafsilot yetishmayapti: t.Sum += amount qatori — O'QISH va YOZISHNI ikkita alohida amal sifatida bajaradi (avval eski qiymatni o'qiydi, keyin yangisini yozadi). Agar ikkita goroutine bu qatorni AYNAN bir vaqtda bajarsa, ikkalasi ham BIR XIL eski qiymatni o'qib, ikkalasi ham o'zining qo'shimchasini qo'shib yozadi — natijada BITTA qo'shish YO'QOLIB ketadi.

  • Bu xato HAR SAFAR ko'rinmaydi — kichik ro'yxatlarda yoki sekin mashinada goroutine'lar kamdan-kam to'qnashadi, shuning uchun test ko'pincha "o'tadi"
  • Xato faqat KATTA ro'yxatlarda, KUCHLI YUKLAMADA yoki go test -race bayrog'i bilan ishga tushirilganda aniq ko'rinadi — bu Systematic Debugging kursidagi "faqat kuchli yuklamada sodir bo'ladigan xato" belgisi
  • To'g'ri kod: OrderTotal struct'iga sync.Mutex qo'shib, Add metodida t.mu.Lock(); defer t.mu.Unlock() bilan yozishni himoyalash, YOKI har bir goroutine natijasini kanal orqali yuborib, BITTA joyda (asosiy goroutine'da) yig'ish

Review'da race condition'ni topishning eng ishonchli usuli — kodni o'qib "bu yerda bir nechta goroutine BIR XIL o'zgaruvchiga yozyaptimi" deb so'rash, va agar javob "ha" bo'lsa, mutex yoki kanal himoyasi BORLIGINI tekshirish. Bundan tashqari, real loyihalarda go test -race bayrog'ini CI (avtomatik tekshiruv) tizimiga qo'shish — bu turdagi xatoni inson ko'zidan qochib ketishidan oldin avtomatik ushlaydi.

Key Takeaway

Key Takeaway:

Bir nechta goroutine bir xil o'zgaruvchiga mutex yoki kanalsiz yozsa — bu race condition, hatto sync.WaitGroup to'g'ri ishlatilgan bo'lsa ham (WaitGroup faqat KUTISHNI, YOZISHNI HIMOYALASHNI emas, ta'minlaydi). Bu xato kamdan-kam, faqat kuchli yuklamada ko'rinadi.

NEXT UP

The Resource That Never Closes