WaitGroup ба Mutex
Горутинүүд нэгэн зэрэг ажиллахад хоёр нийтлэг асуудал гардаг: бүх горутин дуустал хэрхэн хүлээх, хуваалцсан өгөгдлийг хэрхэн аюулгүй өөрчлөх. sync package-ийн WaitGroup ба Mutex яг эдгээр асуудлыг шийддэг.
sync.WaitGroup — горутин хүлээх
time.Sleep ашиглан горутин хүлээх нь найдваргүй. WaitGroup нь тохирох хэмжээний хугацааг хүлээдэг:
package main
import (
"fmt"
"sync"
)
func download(url string, wg *sync.WaitGroup) {
defer wg.Done() // функц дуусахад -1 хийнэ
fmt.Printf("Татаж байна: %s\n", url)
}
func main() {
var wg sync.WaitGroup
urls := []string{
"https://example.com/file1",
"https://example.com/file2",
"https://example.com/file3",
}
for _, url := range urls {
wg.Add(1) // тоолуур +1
go download(url, &wg)
}
wg.Wait() // бүх горутин дуустал хүлээ
fmt.Println("Бүх таталт дууслаа")
}
wg.Add(1) — горутин эхлэхийн өмнө тоолуурыг нэмнэ.
wg.Done() — горутин дуусахад тоолуурыг хасна (defer ашигласан нь зөв хэв маяг).
wg.Wait() — тоолуур тэг болтол хаагдана.
Race Condition — аюулт нөхцөл байдал
Олон горутин нэгэн зэрэг нэг хувьсагчийг өөрчилвөл race condition үүснэ:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
count := 0 // хуваалцсан хувьсагч
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
count++ // АЮУЛТ! Нэгэн зэрэг өөрчилж байна
}()
}
wg.Wait()
fmt.Println("Тоо (буруу байж болно):", count)
// Хүлээгдэж буй: 1000, бодит: тодорхойгүй
}
count++ нь дотроо гурван үйлдэл: унших, нэмэх, бичих. Горутинүүд эдгээр үйлдлийн хооронд огтлолцож болно.
sync.Mutex — хамгаалагч түгжээ
Mutex (Mutual Exclusion) нь нэгэн зэрэг зөвхөн нэг горутинд нэвтрэх боломж олгодог:
package main
import (
"fmt"
"sync"
)
type SafeCounter struct {
mu sync.Mutex
count int
}
func (c *SafeCounter) Increment() {
c.mu.Lock() // түгжих — бусад горутин энд хүлээнэ
c.count++
c.mu.Unlock() // тайлах — бусад горутин үргэлжилнэ
}
func (c *SafeCounter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}
func main() {
var wg sync.WaitGroup
counter := &SafeCounter{}
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait()
fmt.Println("Тоо (зөв):", counter.Value()) // Үргэлж 1000
}
mu.Lock() ба mu.Unlock() хоёрын хооронд нэгэн зэрэг зөвхөн нэг горутин байж болно.
sync.RWMutex — уншилт/бичилтийн түгжээ
Уншилт олон горутинд нэгэн зэрэг зөвшөөрч, бичилтийг хязгаарлах:
package main
import (
"fmt"
"sync"
)
type Cache struct {
mu sync.RWMutex
data map[string]string
}
func (c *Cache) Set(key, value string) {
c.mu.Lock() // бичилтэд бүтэн түгжээ
defer c.mu.Unlock()
c.data[key] = value
}
func (c *Cache) Get(key string) (string, bool) {
c.mu.RLock() // уншилтад зөвхөн уншилтын түгжээ
defer c.mu.RUnlock()
val, ok := c.data[key]
return val, ok
}
func main() {
cache := &Cache{data: make(map[string]string)}
var wg sync.WaitGroup
// Нэгэн зэрэг бичих
for i := 0; i < 5; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
cache.Set(fmt.Sprintf("түлхүүр%d", n), fmt.Sprintf("утга%d", n))
}(i)
}
wg.Wait()
// Нэгэн зэрэг унших
for i := 0; i < 5; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
val, _ := cache.Get(fmt.Sprintf("түлхүүр%d", n))
fmt.Printf("түлхүүр%d = %s\n", n, val)
}(i)
}
wg.Wait()
}
RWMutex нь уншилт их, бичилт цөөн тохиолдолд Mutex-ээс хурдан ажилладаг.
Дараагийн хичээлд:
Go кодыг package болгон зохион байгуулах үндсийг сурна. Package нь кодыг дахин ашиглах, тусгаарлах гол механизм юм.