الآلية الأساسية لإدارة الحالة في Go هي التواصل عبر القنوات. رأينا ذلك مثلاً مع أحواض العمال (worker pools). ومع ذلك، هناك خيارات أخرى لإدارة الحالة. هنا سننظر في استخدام حزمة sync/atomic لإنشاء عدادات ذرية (atomic counters) يتم الوصول إليها بواسطة عدة goroutines.
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
سنستخدم نوعاً صحيحاً ذرياً لتمثيل عدادنا (دائماً موجب).
var ops atomic.Uint64
ستساعدنا WaitGroup في انتظار انتهاء جميع الـ goroutines من عملها.
var wg sync.WaitGroup
سنبدأ 50 goroutines تقوم كل منها بزيادة العداد 1000 مرة بالضبط.
for i := 0; i < 50; i++ {
wg.Add(1)
go func() {
for j := 0; j < 1000; j++ {
لزيادة العداد بشكل ذري نستخدم Add.
ops.Add(1)
}
wg.Done()
}()
}
انتظر حتى تنتهي جميع الـ goroutines.
wg.Wait()
هنا لا توجد goroutines تكتب في ops ولكن باستخدام Load فمن الآمن قراءة القيمة ذرياً حتى أثناء قيام goroutines أخرى بتحديثها (ذرياً).
fmt.Println("ops:", ops.Load())
}
نتوقع الحصول على 50,000 عملية بالضبط. لو استخدمنا رقماً صحيحاً عادياً وقمنا بزيادته بـ ops++ لربما حصلنا على رقم مختلف يتغير بين مرات التشغيل، لأن الـ goroutines ستتداخل مع بعضها البعض. علاوة على ذلك، كنا سنحصل على أخطاء “سباق البيانات” (data race) عند التشغيل مع علم -race.
تشغيل البرنامج:
$ go run atomic-counters.go
ops: 50000 المثال التالي: أقفال الاستبعاد المتبادل (Mutexes).