66 / 73

Data races

Bir nechta goroutine bitta o'zgaruvchiga bir vaqtda murojaat qilsa va ulardan kamida bittasi ma'lumot yozsa, nima yuz beradi? Bu concurrent dasturlashning eng makkor va xavfli muammosi — **Data Race (Ma'lumotlar poygasi)**dir.

Bob mundarijasi:


Parallel modifikatsiya muammosi

Quyidagi oddiy hisoblagichni ko'rib chiqamiz:

example.go
package main

import (
	"fmt"
	"sync"
)

func main() {
	counter := 0
	var wg sync.WaitGroup

	// 1000 ta goroutine counter ni bittadan oshiradi:
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			counter++
		}()
	}

	wg.Wait()
	fmt.Println("Yakuniy natija:", counter)
}

Kutilgan natija: 1000. Ammo ushbu kodni bir necha marta ishga tushirsangiz, har safar har xil kutilmagan javob chiqadi: 942, 987, 965...

Nima uchun 1000 chiqmadi? Chunki counter++ amali bitta atomik amal emas! U mashina darajasida uchta bosqichdan iborat:

  1. Xotiradan qiymatni o'qib olish (READ);
  2. Unga 1 ni qo'shish (INCREMENT);
  3. Yangi qiymatni xotiraga qayta yozish (WRITE).

Agar ikkita goroutine bir vaqtda xotiradan 10 sonini o'qib olsa, har ikkisi unga 1 qo'shib 11 qiladi va xotiraga ketma-ket yozadi. Natijada ikkita amal bajarilgan bo'lsa-da, hisoblagich faqat 1 taga oshadi!


sync.Mutex

Bunday noxush holatni bartaraf etish uchun Go standart kutubxonasida Mutex (Mutual Exclusion — o'zaro istisno) mavjud:

example.go
package main

import (
	"fmt"
	"sync"
)

func main() {
	counter := 0
	var mu sync.Mutex
	var wg sync.WaitGroup

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()

			mu.Lock()         // qulfni yopish (boshqa goroutine'lar navbatda kutadi)
			counter++         // xavfsiz muhim hudud (critical section)
			mu.Unlock()       // qulfni ochish
		}()
	}

	wg.Wait()
	fmt.Println("Xavfsiz yakuniy natija:", counter) // Har doim aniq 1000!
}

mu.Lock() va mu.Unlock() oralig'iga muhim hudud (critical section) deyiladi. Bir vaqtning o'zida ushbu hududda faqat bitta goroutine bo'lishi qat'iy kafolatlanadi.


sync.RWMutex

Agar sizda ma'lumotlar o'qilishi juda ko'p (99%), lekin yozilishi kam bo'lsa (1%), oddiy Mutex barcha o'quvchilarni ham navbatga turg'izib dasturni sekinlashtiradi.

Bunday holatlar uchun sync.RWMutex mavjud:

  • RLock() va RUnlock(): O'qish uchun qulf. Bir vaqtda yuzlab goroutine'lar parallel o'qishi mumkin!
  • Lock() va Unlock(): Yozish uchun qulf. Faqat bitta goroutine yoza oladi, barcha o'quvchi va yozuvchilarni to'xtatib turadi.
example.go
type SafeMap struct {
	mu sync.RWMutex
	data map[string]string
}

func (m *SafeMap) Get(key string) string {
	m.mu.RLock()         // parallel o'qish mumkin
	defer m.mu.RUnlock()
	return m.data[key]
}

func (m *SafeMap) Set(key, value string) {
	m.mu.Lock()          // faqat bitta yozuvchi
	defer m.mu.Unlock()
	m.data[key] = value
}

Go Race Detector

Dasturda data race bor-yo'qligini qanday aniqlash mumkin? Go kompilyatori o'z ichiga ajoyib vosita — Race Detectorni olgan! Shunchaki dasturni quyidagicha ishga tushiring:

bash
go run -race main.go
go test -race ./...

Agar kodda ma'lumotlar poygasi bo'lsa, u qaysi goroutine va qaysi qatorda xotiraga noxolis murojaat qilganini aniq ko'rsatib beradi.


Topshiriq: Xavfsiz hisoblagich

SafeCounter strukturasini tuzing va unga Inc() hamda Value() int metodlarini qo'shing. Dasturni poygasiz ishlaydigan holatga keltiring.

example.go
package main

import (
	"fmt"
	"sync"
)

type SafeCounter struct {
	// TODO: mutex va count maydonlarini qo'shing
}

func (c *SafeCounter) Inc() {
	// TODO: mutex bilan xavfsiz oshiring
}

func (c *SafeCounter) Value() int {
	// TODO: xavfsiz qiymatni qaytaring
	return 0
}

func main() {
	c := SafeCounter{}
	var wg sync.WaitGroup

	for i := 0; i < 500; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			c.Inc()
		}()
	}

	wg.Wait()
	fmt.Println("Natija:", c.Value())
}

Xulosa va keyingi qadamlar

Data race Go concurrent dasturlashda eng ko'p yo'l qo'yiladigan xatolardan biri bo'lib, sync.Mutex va sync.RWMutex uni bartaraf etishning eng mustahkam poydevoridir.

Keyingi mavzu: Race conditions (Mantiqiy poyga holatlari va Check-then-act)!