Go by Example မြန်မာဘာသာ: Atomic Counters

Go မှာ state ကို စီမံခန့်ခွဲဖို့ အဓိက နည်းလမ်းကတော့ channel တွေကနေ တဆင့် ဆက်သွယ်မှုပါ။ ဒါကို worker pools မှာ ဥပမာအနေနဲ့ တွေ့ခဲ့ပါတယ်။ ဒါပေမယ့် state စီမံခန့်ခွဲဖို့ တခြားနည်းလမ်းအနည်းငယ်လည်း ရှိပါသေးတယ်။ ဒီမှာတော့ goroutine အများကြီးက တပြိုင်နက်တည်း access လုပ်တဲ့ atomic counter တွေအတွက် sync/atomic package ကို သုံးတာကို ကြည့်ကြပါမယ်။

package main
import (
    "fmt"
    "sync"
    "sync/atomic"
)
func main() {

ကျွန်တော်တို့ရဲ့ (အမြဲတမ်း အပေါင်းကိန်းဖြစ်တဲ့) counter ကို ကိုယ်စားပြုဖို့ unsigned integer တစ်ခုကို သုံးပါမယ်။

    var ops uint64

WaitGroup က goroutine အားလုံး အလုပ်ပြီးတဲ့အထိ စောင့်ဖို့ ကူညီပါလိမ့်မယ်။

    var wg sync.WaitGroup

Goroutine 50 ခုကို စတင်ပါမယ်။ တစ်ခုချင်းစီက counter ကို တိတိကျကျ 1000 ကြိမ်စီ တိုးပါလိမ့်မယ်။

    for i := 0; i < 50; i++ {
        wg.Add(1)

Counter ကို atomic ဖြစ်အောင် တိုးဖို့ AddUint64 ကို သုံးပါတယ်။ & syntax နဲ့ ကျွန်တော်တို့ရဲ့ ops counter ရဲ့ memory address ကို ပေးလိုက်ပါတယ်။

        go func() {
            for c := 0; c < 1000; c++ {
                atomic.AddUint64(&ops, 1)
            }
            wg.Done()
        }()
    }

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

    wg.Wait()

အခုဆိုရင် ops ကို access လုပ်ဖို့ လုံခြုံပါပြီ။ ဘာလို့လဲဆိုတော့ တခြား goroutine တွေက သူ့ကို ရေးနေတာ မရှိတော့ဘူးဆိုတာ သိလို့ပါ။ Atomic တွေကို update လုပ်နေချိန်မှာ safely read လုပ်လို့ရပါတယ်။ atomic.LoadUint64 လို function တွေကို သုံးပြီး လုပ်လို့ရပါတယ်။

    fmt.Println("ops:", ops)
}

ကျွန်တော်တို့က တိတိကျကျ operation 50,000 ရဖို့ မျှော်လင့်ပါတယ်။ အကယ်၍သာ counter ကိုတိုးဖို့ non-atomic ops++ ကိုသုံးခဲ့မယ်ဆိုရင်၊ ကျွန်တော်တို့ ကွဲပြားတဲ့နံပါတ်တစ်ခု ရဖို့များပါတယ်။ ဒီနံပါတ်ကလည်း run တိုင်း ပြောင်းလဲနေနိုင်ပါတယ်။ ဘာကြောင့်လဲဆိုတော့ goroutine တွေက တစ်ခုနဲ့တစ်ခု ဝင်ရောက်စွက်ဖက်နိုင်လို့ပါ။ ဒါ့အပြင် -race flag နဲ့ run ရင် data race failure တွေ ရနိုင်ပါတယ်။

$ go run atomic-counters.go
ops: 50000

နောက်တစ်ဆင့်မှာ state စီမံခန့်ခွဲရေးအတွက် နောက်ထပ် tool တစ်ခုဖြစ်တဲ့ mutex တွေအကြောင်း လေ့လာကြပါမယ်။

နောက်ဥပမာ: Mutexes.