3.4 پکیج atomic

3.4 پکیج atomic

پکیج atomic یک حافظه atomic سطح پایین برای پیاده سازی الگوریتم های همگام سازی شده است. از مواردی که خیلی قابل اهمیت است با این پکیج شما می توانید یکسری الگوهای همگام سازی را پیاده سازی کنید. سعی کنید با دقت بیشتری از این پکیج استفاده کنید چون کارکردش خارج از safe memory هست.

به مثال زیر توجه کنید :

 1package main
 2
 3import (
 4	"fmt"
 5	"sync"
 6	"sync/atomic"
 7)
 8
 9type Cache struct {
10	mu   sync.Mutex
11	data map[string]string
12}
13
14func (c *Cache) Set(key, value string) {
15	c.mu.Lock()
16	defer c.mu.Unlock()
17	c.data[key] = value
18}
19
20func (c *Cache) Get(key string) (value string, ok bool) {
21	c.mu.Lock()
22	defer c.mu.Unlock()
23	value, ok = c.data[key]
24	return
25}
26
27type AtomicCache struct {
28	mu   sync.Mutex
29	data atomic.Value
30}
31
32func (c *AtomicCache) Set(key, value string) {
33	c.mu.Lock()
34	defer c.mu.Unlock()
35	c.data.Store(map[string]string{key: value})
36}
37
38func (c *AtomicCache) Get(key string) (value string, ok bool) {
39	data := c.data.Load().(map[string]string)
40	value, ok = data[key]
41	return
42}
43
44func main() {
45	cache := Cache{data: map[string]string{}}
46	cache.Set("key", "value")
47	fmt.Println(cache.Get("key")) // Output: value, true
48
49	atomicCache := AtomicCache{data: atomic.Value{}}
50	atomicCache.Set("key", "value")
51	fmt.Println(atomicCache.Get("key")) // Output: value, true
52}
1$ go run main.go
2value true
3value true

در مثال فوق ما یک ساختار به نام Cache داریم که داخلش یک فیلد از نوع map داریم و قصد داریم یکسری اطلاعات را داخل کش بریزیم حال زمانیکه Set/Get می کنیم با استفاده از Mutex اون بخش از عملیات را لاک میکنیم تا جلوی عملیات نوشتن چندین گوروتین برروی یک آدرس حافظه را بگیریم. حال این عملیات رو ما با استفاده از atomic انجام دادیم و همگام سازی داده را بردیم تو سطح خیلی پایین تر در حافظه و با استفاده از atomic.Value که یک اینترفیس است این عملیات را انجام دادیم و این عملیات Set/Get حالت atomic پیدا کرده است.

آیا استفاده از atomic نیازمند mutex می باشد یا خیر؟

در این کد، mutex در متد Set برای جلوگیری از رخ دادن race condition یا داده‌های نامنظم استفاده شده است. بدون mutex، چندین گوروتین ممکن است همزمان به دسترسی و تغییر داده‌های map data بپردازند که موجب رفتار نامنظم و فساد داده می‌شود. با گرفتن mutex قبل از تغییر map data، متد Set اطمینان حاصل می‌کند که تنها یک گوروتین در هر زمان می‌تواند به داده‌ها دسترسی پیدا کند و تداخل داده‌ها را جلوگیری می‌کند.

استفاده از mutex در متد Get نیز مهم است، زیرا این اطمینان را به ما می‌دهد که در هنگام دسترسی به map data، هیچ گوروتین دیگری دارای مجوز تغییر داده‌ها نیست. بدون mutex، یک race condition ممکن است ایجاد شود اگر یک گوروتین دیگر در حال تغییر داده‌های map باشد در حالی که یک گوروتین دیگر سعی در خواندن از آن دارد.

در پیاده‌سازی AtomicCache، یک atomic.Value برای ذخیره map استفاده شده است که به انجام عملیات اتمی روی آن اجازه می‌دهد. با این حال، حتی با استفاده از یک مقدار اتمی، همچنان نیاز به mutex وجود دارد تا فقط یک گوروتین در هر زمان به map دسترسی داشته باشد و تداخل داده‌ها را جلوگیری کند.

3.4.1 برخی از کاربردهای atomic #

در زیر چندتا use case برای استفاده از پکیج atomic معرفی کردیم :

  1. پیاده سازی همگام سازی بدون مسدودیت : پکیج atomic توابع سطح پایینی را برای انجام عملیات حافظه اتمی فراهم می کند که می تواند برای پیاده سازی الگوریتم های همگام سازی غیرمسدود مانند مقایسه و تعویض (CAS) یا بارگذاری لینک/ذخیره شرطی استفاده شود. LL/SC).

  2. پیاده سازی ساختارهای داده با همزمانی سطح (high-concurrency) بالا : با پکیج atomic می توان برای پیاده سازی ساختارهای داده ای استفاده کرد که برای دسترسی همزمان و اصلاح توسط چندین گوروتین ایمن هستند. به عنوان مثال، می توانید از بسته اتمی برای پیاده سازی نقشه یا صف همزمان استفاده کنید.

  3. پیاده سازی شمارنده (counter) از نوع atomic : شما با استفاده از پکیج atomic می توانید برای افزایش و کاهش شمارنده ها به صورت اتمی که می تواند برای اجرای مواردی مانند شمارش مرجع یا محدود کردن ratelimit استفاده شود.