Go by Example မြန်မာဘာသာ: Mutexes

ပြီးခဲ့တဲ့ဥပမာမှာ atomic operation တွေသုံးပြီး ရိုးရှင်းတဲ့ counter state ကို ဘယ်လိုစီမံခန့်ခွဲသလဲဆိုတာ တွေ့ခဲ့ပါတယ်။ ပိုရှုပ်ထွေးတဲ့ state တွေအတွက်တော့ goroutine အများကြီးကြားထဲမှာ ဒေတာကို လုံခြုံစွာ access လုပ်ဖို့ mutex ကို သုံးနိုင်ပါတယ်။

package main
import (
    "fmt"
    "sync"
)

Container က counter တွေရဲ့ map ကို သိမ်းထားပါတယ်။ ဒီ map ကို goroutine အများကြီးကနေ တပြိုင်နက်တည်း update လုပ်ချင်တဲ့အတွက် access ကို synchronize လုပ်ဖို့ Mutex တစ်ခု ထည့်ထားပါတယ်။ သတိပြုရမှာက mutex တွေကို copy မလုပ်သင့်ပါဘူး၊ ဒါကြောင့် ဒီ struct ကို တခြားနေရာတွေဆီ ပို့မယ်ဆိုရင် pointer နဲ့ပို့သင့်ပါတယ်။

type Container struct {
    mu       sync.Mutex
    counters map[string]int
}

counters ကို access မလုပ်ခင် mutex ကို lock လုပ်ပါ။ function အဆုံးမှာ unlock လုပ်ဖို့ defer statement ကို သုံးထားပါတယ်။

func (c *Container) inc(name string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.counters[name]++
}

Mutex ရဲ့ zero value က အသုံးပြုလို့ရပါတယ်၊ ဒါကြောင့် ဒီမှာ သီးခြား initialization လုပ်စရာမလိုပါဘူး။

func main() {
    c := Container{
        counters: map[string]int{"a": 0, "b": 0},
    }
    var wg sync.WaitGroup

ဒီ function က နာမည်ပေးထားတဲ့ counter ကို loop ထဲမှာ တိုးပေးပါတယ်။

    doIncrement := func(name string, n int) {
        for i := 0; i < n; i++ {
            c.inc(name)
        }
        wg.Done()
    }

Goroutine အများကြီးကို တပြိုင်နက်တည်း run တာပါ။ သတိပြုရမှာက သူတို့အားလုံးက တူညီတဲ့ Container ကိုပဲ access လုပ်နေပြီး နှစ်ခုကတော့ တူညီတဲ့ counter ကိုပဲ access လုပ်နေပါတယ်။

    wg.Add(3)
    go doIncrement("a", 10000)
    go doIncrement("a", 10000)
    go doIncrement("b", 10000)

Goroutine တွေ ပြီးဆုံးတဲ့အထိ စောင့်ပါ

    wg.Wait()
    fmt.Println(c.counters)
}

ပရိုဂရမ်ကို run လိုက်တဲ့အခါ counter တွေက ကျွန်တော်တို့ မျှော်လင့်ထားသလိုပဲ update ဖြစ်သွားတာကို တွေ့ရပါတယ်။

$ go run mutexes.go
map[a:20000 b:10000]

နောက်တစ်ဆင့်မှာ ဒီလို state စီမံခန့်ခွဲမှုကိုပဲ goroutine တွေနဲ့ channel တွေကိုပဲ သုံးပြီး အကောင်အထည်ဖော်တာကို ကြည့်ကြပါမယ်။

နောက်ဥပမာ: Stateful Goroutines.