70 / 73

Atomics

Mutex qulfi ishonchli va xavfsiz, ammo u operatsion tizim darajasida goroutine'ni uyquga ketkazish va uyg'otish kabi qo'shimcha xarajatlarga ega. Agar sizga faqat oddiy hisoblagichni (counter), bayroqchani (boolean flag) yoki ko'rsatkichni xavfsiz yangilash kerak bo'lsa, Atomik amallar (Atomics) bundan ancha tezroq va samaraliroq ishlaydi!

Bob mundarijasi:


Atomik amallar nima?

Atomik amal (grekcha atomos — bo'linmas) — bu CPU darajasida bitta apparat ko'rsatmasi (machine instruction) bilan bajariladigan va boshqa hech bir oqim tomonidan chala holatda ko'rilishi mumkin bo'lmagan bo'linmas amaldir.

Oddiy x++ xotiradan o'qish, qo'shish va qayta yozishdan iborat 3 ta alohida amaldan tashkil topgan bo'lsa, atomik amal apparat darajasida xotira shinasini (memory bus / cache line) bloklab, bu uchtasini bitta butun sifatida bajaradi.


sync/atomic va zamonaviy turlar

Go 1.19 dan boshlab atomik amallar bilan ishlash ancha qulaylashdi. Endi xom pointerlar bilan emas, balki qulay metodlarga ega tiplar bilan ishlanadi:

  • atomic.Int64 / atomic.Int32
  • atomic.Uint64 / atomic.Uint32
  • atomic.Bool
  • atomic.Pointer[T]
example.go
package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	var counter atomic.Int64
	var wg sync.WaitGroup

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			counter.Add(1) // hech qanday Mutex shart emas, mutlaqo poygasiz!
		}()
	}

	wg.Wait()
	fmt.Println("Yakuniy natija:", counter.Load()) // Har doim 1000!
}

Asosiy metodlar:

  • counter.Add(delta) — qiymatni oshirish yoki kamaytirish;
  • counter.Load() — qiymatni xavfsiz o'qish;
  • counter.Store(val) — qiymatni xavfsiz yozish;
  • counter.Swap(val) — yangi qiymat yozib, eski qiymatni qaytarish;
  • counter.CompareAndSwap(old, new) — agar qiymat old ga teng bo'lsa, uni new ga almashtirish.

Compare-and-Swap (CAS)

CAS — bu barcha lock-free (qulfsiz) ma'lumotlar tuzilmalarining (lock-free queue, stack) poydevoridir:

example.go
var max atomic.Int64

func updateMax(val int64) {
	for {
		current := max.Load()
		if val <= current {
			return
		}
		// Agar max o'rtada boshqa goroutine tomonidan o'zgartirilmagan bo'lsa,
		// uni val ga almashtiramiz:
		if max.CompareAndSwap(current, val) {
			return // muvaffaqiyatli yangilandi
		}
		// agar o'zgarib ketgan bo'lsa, sikl qaytadan aylanadi
	}
}

Mutex o'rniga Atomic: Qachon qaysi biri?

XususiyatAtomicsMutex
Mos keladigan holatBitta o'zgaruvchi (hisoblagich, flag, pointer)Murakkab strukturalar, bir nechta o'zaro bog'liq maydonlar
TezlikJuda yuqori (CPU apparat darajasida)Yuqori, ammo goroutine kontekst almashtirishi mumkin
Deadlock xavfiYo'qEhtiyotsizlik qilinsa mavjud

Topshiriq: Lock-free hisoblagich

atomic.Int64 yordamida bir vaqtda 500 ta goroutine xavfsiz oshiradigan va kamaytiradigan hisoblagich tuzing.

example.go
package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	var count atomic.Int64
	var wg sync.WaitGroup

	// TODO: 500 ta goroutine Add(1) qilsin, 200 tasi Add(-1) qilsin
	// wg.Wait()

	fmt.Println("Hisoblagich:", count.Load())
}

Xulosa va keyingi qadamlar

Shu bilan 02. Synchronization bo'limi to'liq yakunlandi!

Keyingi bo'lim: 03. Other topics — Concurrent Go dasturlarini professional testlash (testing) va Go runtime scheduler'ining ichki ishlash mexanizmlari (internals)!