Go با مثال: شمارنده‌های اتمیک

سازوکار اصلی مدیریت وضعیت (state) در Go، ارتباط از طریق کانال‌هاست. نمونه‌اش را در استخر کارگرها دیدیم. اما گزینه‌های دیگری هم برای مدیریت وضعیت وجود دارد. اینجا استفاده از بستهٔ sync/atomic را برای شمارنده‌های اتمیک می‌بینیم که چند گوروتین به آن‌ها دسترسی دارند.

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

از یک نوع عدد صحیح اتمیک برای نمایش شمارندهٔ (همیشه مثبتِ) خودمان استفاده می‌کنیم.

    var ops atomic.Uint64

یک WaitGroup کمک می‌کند منتظر بمانیم تا همهٔ گوروتین‌ها کارشان را تمام کنند.

    var wg sync.WaitGroup

۵۰ گوروتین راه می‌اندازیم که هر کدام شمارنده را دقیقاً ۱۰۰۰ بار افزایش می‌دهند.

    for range 50 {
        wg.Go(func() {
            for range 1000 {

برای افزایش اتمیک شمارنده از Add استفاده می‌کنیم.

                ops.Add(1)
            }
        })
    }

صبر می‌کنیم تا همهٔ گوروتین‌ها تمام شوند.

    wg.Wait()

اینجا هیچ گوروتینی در ‘ops’ نمی‌نویسد، اما با Load خواندن اتمیک یک مقدار امن است، حتی وقتی گوروتین‌های دیگر در حال به‌روزرسانی (اتمیک) آن هستند.

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

انتظار داریم دقیقاً ۵۰٬۰۰۰ عملیات داشته باشیم. اگر از یک عدد صحیح غیراتمیک استفاده می‌کردیم و آن را با ops++ افزایش می‌دادیم، احتمالاً عدد دیگری می‌گرفتیم که در هر اجرا تغییر می‌کرد، چون گوروتین‌ها با هم تداخل پیدا می‌کردند. علاوه بر این، هنگام اجرا با فلگ -race با خطای data race روبه‌رو می‌شدیم.

$ go run atomic-counters.go
ops: 50000

در ادامه میوتکس‌ها را می‌بینیم؛ ابزار دیگری برای مدیریت وضعیت.

مثال بعدی: .