Go: أساسيات التزامن في Go

تُستخدم القنوات للتواصل بين الغوروتينات، ويُستخدم حزمة sync لحماية الحالة المشتركة — توفر لغة Go مجموعة أدوات كاملة للبرمجة المتزامنة.

عندما تحتاج عدة «غوروتينات» إلى القراءة من متغير واحد أو الكتابة إليه، يصبح من الضروري استخدام عناصر التزامن الأساسية لضمان السلامة. في هذا الدرس، ستتعرف على جميع الأدوات الأساسية واستراتيجيات الاختيار الخاصة بحزمة sync في لغة Go.

1. ستتعلم



2. قصة حقيقية لمهندس متخصص في أنظمة التزامن العالي

(1) المشكلة: تقوم 100 «غوروتين» بالكتابة إلى العداد في وقت واحد، مما يؤدي إلى تلف البيانات تمامًا

تشارلي هو مهندس «الخلفية» في منصة للتجارة الإلكترونية، ويحتاج إلى حساب عدد الطلبات في الثانية:

«كانت 100 غوروتين تعالج الطلبات في وقت واحد، وكانت كل واحدة منها تزيد العداد بمقدار 1 بعد إتمام طلب. في اليوم الأول بعد الإطلاق، كانت البيانات غير دقيقة تمامًا — فقد أظهرت لوحة التحكم 1,000 طلب في الثانية، لكن لم يكن هناك سوى 300 طلب في قاعدة البيانات. سألني مديري: «أين ذهب المال الخاص بتلك الطلبات الـ700؟»»

فتح الملف البرمجي:

GO
// Bad code: concurrent write to shared variable, no lock
var counter int

func processOrder(orderID string) {
    // ... process order
    counter++  // Non-atomic operation! Equivalent to:
               // temp = counter
               // temp = temp + 1
               // counter = temp  <- 3 goroutines executing this step simultaneously = chaos
}

counter++ ليست عملية ذرية — فهي تنقسم إلى ثلاث خطوات: القراءة، والزيادة بمقدار واحد، والكتابة. إذا قامت جروتينتان في الوقت نفسه بقراءة counter=10، وزيادتها بمقدار واحد، ثم إعادة كتابتها، فستكون النتيجة counter=11 بدلاً من 12 — وهذا مثال كلاسيكي على تنافس البيانات.

(2) الحل بلغة Go: Mutex

GO
// counter.go
package main

import (
    "fmt"
    "sync"
)

type SafeCounter struct {
    mu    sync.Mutex
    القيمة int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    c.القيمة++ // Protected by the lock; only one goroutine can execute at a time
    c.mu.Unlock()
}

func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.القيمة
}

func main() {
    var counter SafeCounter
    var wg sync.WaitGroup

    // 100 goroutines incrementing simultaneously
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }

    wg.Wait()
    fmt.Printf("Final القيمة: %d (expected 100)\n", counter.Value())
}

الناتج:

TEXT 📖 للعرض فقط
Final value: 100 (expected 100)

(3) العوائد: المقيدة مقابل غير المقيدة

الحالة النتيجة تضارب البيانات
التزامن بدون قفل 95/100/97 (عشوائي) ✅ نعم (تم الكشف عن تفرع سباق)
حماية الموتكس 100 ❌ لا شيء
عملية ذرية 100 ❌ لا شيء
💡 نصيحة: يمكن لـ go run -race main.go اكتشاف حالات التنافس على البيانات. إذا لم يكن الوصول المتزامن إلى المتغيرات المشتركة محميًّا بأقفال، فسيصدر الخيار -race تحذيرًا أثناء وقت التشغيل. يُنصح دائمًا بتمكين -race في CI/CD.



3. sync.Mutex Mutex

(1) أساسيات الموتكس

GO
package main

import (
    "fmt"
    "sync"
)

type Account struct {
    mu      sync.Mutex
    balance float64
}

func (a *Account) Deposit(amount float64) {
    a.mu.Lock()
    a.balance += amount
    a.mu.Unlock()
}

func (a *Account) Withdraw(amount float64) error {
    a.mu.Lock()
    defer a.mu.Unlock()
    if a.balance < amount {
        return fmt.Errorf("insufficient balance: have %.2f, need %.2f", a.balance, amount)
    }
    a.balance -= amount
    return nil
}

func (a *Account) Balance() float64 {
    a.mu.Lock()
    defer a.mu.Unlock()
    return a.balance
}

func main() {
    acc := Account{balance: 1000}
    var wg sync.WaitGroup

    // 10 concurrent deposits
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            acc.Deposit(100)
        }()
    }
    wg.Wait()

    fmt.Printf("Balance: %.2f\n", acc.Balance())
}

▶ مثال: تأجيل نمط فتح القفل (موصى به)

GO
// Recommended: Lock then immediately defer Unlock
func (a *Account) SafeMethod() {
    a.mu.Lock()
    defer a.mu.Unlock()
    // All operations are within the lock
    a.balance += 100
    a.balance -= 50
    // Even if there is a panic in between, defer ensures Unlock
}

// Not recommended: manual Unlock
func (a *Account) UnsafeMethod() {
    a.mu.Lock()
    a.balance += 100
    a.mu.Unlock() // If there is a return or panic in between, Unlock won't be called → deadlock
}
▶ جرّب الكود
🔥 خطأ شائع: بعد Lock، يجب استدعاء Unlock. استخدم دائمًا defer a.mu.Unlock() بدلاً من استدعاء Unlock يدويًّا في النهاية — فإذا كانت هناك مسارات عودة متعددة، فإن نسيان استدعاء Unlock في أي منها سيؤدي إلى حدوث حالة تعطل.



4. sync.RWMutex: قفل القراءة والكتابة

(1) مبادئ أقفال القراءة والكتابة

GO
package main

import (
    "fmt"
    "sync"
    "time"
)

type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func NewCache() *Cache {
    return &Cache{data: make(map[string]string)}
}

// Read operation: RLock (concurrent reads, non-exclusive)
func (c *Cache) Get(key string) (string, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    v, ok := c.data[key]
    return v, ok
}

// Write operation: Lock (exclusive, blocks all reads and writes)
func (c *Cache) Set(key, value string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = value
}

func main() {
    cache := NewCache()
    var wg sync.WaitGroup

    // Multiple concurrent reads (RLock is non-exclusive)
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            for j := 0; j < 3; j++ {
                if v, ok := cache.Get("key"); ok {
                    fmt.Printf("Reader %d: %s\n", id, v)
                }
                time.Sleep(10 * time.Millisecond)
            }
        }(i)
    }

    // One writer
    wg.Add(1)
    go func() {
        defer wg.Done()
        time.Sleep(5 * time.Millisecond)
        cache.Set("key", "value")
        fmt.Println("Writer: write complete")
    }()

    wg.Wait()
}

▶ مثال: مقارنة الأداء بين Mutex و RWMutex

GO
package main

import (
    "sync"
    "testing"
)

type DataStore struct {
    mu    sync.RWMutex
    value int
}

func BenchmarkMutexRead(b *testing.B) {
    store := DataStore{}
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            store.mu.Lock()
            _ = store.value
            store.mu.Unlock()
        }
    })
}

func BenchmarkRWMutexRead(b *testing.B) {
    store := DataStore{}
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            store.mu.RLock()
            _ = store.value
            store.mu.RUnlock()
        }
    })
}

// go test -bench=. -benchmem
▶ جرّب الكود

(3) Mutex مقابل RWMutex

ميزة Mutex RWMutex
القراءة والقراءة الاستبعاد المتبادل عدم الاستبعاد المتبادل
القراءة والكتابة الاستبعاد المتبادل الاستبعاد المتبادل
الكتابة والكتابة الاستبعاد المتبادل الاستبعاد المتبادل
حالات الاستخدام نسبة القراءة إلى الكتابة 1:1 تتجاوز عمليات القراءة عمليات الكتابة بكثير (مثل ذاكرة التخزين المؤقت)
أداء القراءة بطيء (متسلسل) سريع (قراءات متزامنة)


5. sync.Once: التنفيذ مرة واحدة

(1) «Once Basics»

GO
package main

import (
    "fmt"
    "sync"
)

var (
    config     map[string]string
    configOnce sync.Once
)

func loadConfig() {
    configOnce.Do(func() {
        fmt.Println("Initializing config...")
        config = map[string]string{
            "host": "localhost",
            "port": "8080",
        }
    })
}

func main() {
    var wg sync.WaitGroup
    // 100 goroutines call simultaneously, but init executes only once
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            loadConfig()
        }()
    }
    wg.Wait()
    fmt.Printf("config: %v\n", config)
}

الناتج:

TEXT 📖 للعرض فقط
Initializing config...
config: map[host:localhost port:8080]

(2) sync.Once مقابل الإشارات اليدوية

الطريقة آمنة للاستخدام المتعدد حجم الكود
if !initialized { ... } ❌ تضارب في الوصول إلى البيانات بسيط
sync.Once ✅ ضمانات Go الحد الأدنى
init() دالة ✅ يتم تنفيذها عند تحميل الحزمة بسيطة
💡 نصيحة: يضمن sync.Once أن الدالة الموجودة داخل Do تُنفَّذ مرة واحدة فقط، حتى لو تم استدعاؤها في وقت واحد من قِبل 1,000 goroutine. ويستخدم التنفيذ الداخلي لهذه الدالة عمليات ذرية وموتكسات، مما يجعلها عالية الكفاءة.



6. sync.Map: خريطة آمنة من حيث التزامن

(1) أساسيات sync.Map

GO
package main

import (
    "fmt"
    "sync"
)

func main() {
    var m sync.Map
    var wg sync.WaitGroup

    // Concurrent writes
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            m.Store(fmt.Sprintf("key-%d", n), n*100)
        }(i)
    }
    wg.Wait()

    // Read
    m.Range(func(key, value interface{}) bool {
        fmt.Printf("%s = %d\n", key, value)
        return true
    })

    // LoadOrStore: returns if exists, stores if not
    actual, loaded := m.LoadOrStore("key-0", 999)
    fmt.Printf("LoadOrStore: actual=%d, loaded=%v\n", actual, loaded)
}

▶ مثال: sync.Map مقابل map+Mutex

GO
package main

import (
    "sync"
    "testing"
)

// map + Mutex
type MutexMap struct {
    mu    sync.Mutex
    item map[string]int
}

func (m *MutexMap) Store(key string, القيمة int) {
    m.mu.Lock()
    m.item[key] = القيمة
    m.mu.Unlock()
}

func (m *MutexMap) Load(key string) (int, bool) {
    m.mu.Lock()
    v, ok := m.item[key]
    m.mu.Unlock()
    return v, ok
}

func BenchmarkMutexMap(b *testing.B) {
    m := &MutexMap{item: make(map[string]int)}
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            m.Store("key", 1)
            m.Load("key")
        }
    })
}

func BenchmarkSyncMap(b *testing.B) {
    var m sync.Map
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            m.Store("key", 1)
            m.Load("key")
        }
    })
}

// go test -bench=. -benchmem
▶ جرّب الكود

(3) حالات استخدام sync.Map

السيناريو التوصية السبب
كتابة المفتاح مرة واحدة sync.Map تحسين القراءة (عمليات كتابة قليلة، عمليات قراءة كثيرة)
تحديثات متكررة للمفاتيح map+Mutex عمليات الكتابة في sync.Map أبطأ قليلاً
توسيع مجموعة المفاتيح sync.Map تجنب قفل الخريطة بأكملها
سيناريو بسيط الخريطة + Mutex أكثر سهولة في الاستخدام
متطلبات الأداء العالي معيار قياس محدد يتم تحديدها من خلال الاختبارات الفعلية


7. sync/atomic: العمليات الذرية

(1) أساسيات الذرة

GO
package main

import (
    "fmt"
    "sync/atomic"
)

func main() {
    var counter int64

    // Atomic increment
    newVal := atomic.AddInt64(&counter, 1)
    fmt.Printf("AddInt64: %d\n", newVal)

    // Atomic read
    val := atomic.LoadInt64(&counter)
    fmt.Printf("LoadInt64: %d\n", val)

    // Atomic write
    atomic.StoreInt64(&counter, 100)

    // CAS (Compare And Swap)
    swapped := atomic.CompareAndSwapInt64(&counter, 100, 200)
    fmt.Printf("CAS: swapped=%v, val=%d\n", swapped, atomic.LoadInt64(&counter))
}

▶ مثال: عدادات الذرات مقابل الموتكسات

GO 📖 للعرض فقط
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
    "time"
)

type AtomicCounter struct {
    value int64
}

func (c *AtomicCounter) Increment() {
    atomic.AddInt64(&c.value, 1)
}

func (c *AtomicCounter) Value() int64 {
    return atomic.LoadInt64(&c.value)
}

type MutexCounter struct {
    mu    sync.Mutex
    value int
}

func (c *MutexCounter) Increment() {
    c.mu.Lock()
    c.value++
    c.mu.Unlock()
}

func (c *MutexCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

func main() {
    var wg sync.WaitGroup
    n := 100000

    // Atomic counter
    atomicStart := time.Now()
    var ac AtomicCounter
    for i := 0; i < n; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            ac.Increment()
        }()
    }
    wg.Wait()
    fmt.Printf("Atomic: %d (%v)\n", ac.Value(), time.Since(atomicStart))

    // Mutex counter
    muStart := time.Now()
    var mc MutexCounter
    for i := 0; i < n; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            mc.Increment()
        }()
    }
    wg.Wait()
    fmt.Printf("Mutex: %d (%v)\n", mc.Value(), time.Since(muStart))
}
56 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

(3) الوظائف الذرية الشائعة

العملية الوظيفة الغرض
جمع/طرح AddInt64، AddUint32 العداد
قراءة LoadInt64، LoadPointer قراءة آمنة
كتابة StoreInt64، StorePointer كتابة آمنة
CAS CompareAndSwapInt64 قفل متفائل
المبادلة SwapInt64 المبادلة الذرية


8. كاشف السباق

▶ مثال: الكشف عن تضارب الوصول إلى البيانات

GO
// race_example.go
package main

import (
    "fmt"
    "sync"
)

func main() {
    var counter int
    var wg sync.WaitGroup

    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter++ // Data race!
        }()
    }

    wg.Wait()
    fmt.Println(counter)
}
▶ جرّب الكود
BASH
$ go run -race race_example.go
==================
WARNING: DATA RACE
Read at 0x00c0000... by goroutine X
  main.main.func1()
      race_example.go:14 +0x...

Previous write at 0x00c0000... by goroutine Y
  main.main.func1()
      race_example.go:14 +0x...
==================


9. sync.Pool — تجمع الكائنات

▶ مثال: تجمع الكائنات

GO
package main

import (
    "fmt"
    "sync"
)

type User struct {
    Name string
    Age  int
}

var userPool = sync.Pool{
    New: func() interface{} {
        return &User{}
    },
}

func main() {
    // Get from pool (avoids allocating a new object each time)
    u := userPool.Get().(*User)
    u.Name = "Alice"
    u.Age = 28
    fmt.Printf("Using: %+v\n", u)

    // Put back into the pool after use
    userPool.Put(u)

    // Next Get reuses the previous object
    u2 := userPool.Get().(*User)
    fmt.Printf("Reusing: %+v\n", u2)
}
▶ جرّب الكود

10. مثال كامل: عداد عالي التزامن

GO
// concurrent_counter.go
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
    "time"
)

// ---------- 3 counter variants ----------

type Counter interface {
    Increment()
    Value() int64
}

// AtomicCounter
type AtomicCounter struct {
    القيمة int64
}

func (c *AtomicCounter) Increment() {
    atomic.AddInt64(&c.القيمة, 1)
}

func (c *AtomicCounter) Value() int64 {
    return atomic.LoadInt64(&c.القيمة)
}

// MutexCounter
type MutexCounter struct {
    mu    sync.Mutex
    القيمة int64
}

func (c *MutexCounter) Increment() {
    c.mu.Lock()
    c.القيمة++
    c.mu.Unlock()
}

func (c *MutexCounter) Value() int64 {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.القيمة
}

// ChannelCounter
type ChannelCounter struct {
    ch    chan struct{}
    القيمة int64
    done  chan struct{}
}

func NewChannelCounter() *ChannelCounter {
    c := &ChannelCounter{
        ch:   make(chan struct{}, 100),
        done: make(chan struct{}),
    }
    go func() {
        for range c.ch {
            c.القيمة++
        }
        close(c.done)
    }()
    return c
}

func (c *ChannelCounter) Increment() {
    c.ch <- struct{}{}
}

func (c *ChannelCounter) Value() int64 {
    return atomic.LoadInt64(&c.القيمة)
}

func (c *ChannelCounter) Close() {
    close(c.ch)
    <-c.done
}

// ---------- Benchmark ----------

func benchmarkCounter(c Counter, goroutines, increments int) time.Duration {
    var wg sync.WaitGroup
    start := time.Now()

    for g := 0; g < goroutines; g++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for i := 0; i < increments; i++ {
                c.Increment()
            }
        }()
    }

    wg.Wait()
    return time.Since(start)
}

func main() {
    const (
        goroutines = 100
        increments = 10000
    )

    fmt.Printf("Benchmark: %d goroutines, each executing %d Increments\n\n", goroutines, increments)

    // 1. Atomic counter
    ac := &AtomicCounter{}
    atomicTime := time.Now()
    atomicDone := make(chan struct{})
    go func() {
        defer close(atomicDone)
        var wg sync.WaitGroup
        for g := 0; g < goroutines; g++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                for i := 0; i < increments; i++ {
                    ac.Increment()
                }
            }()
        }
        wg.Wait()
    }()
    <-atomicDone
    fmt.Printf("AtomicCounter: %d (expected %d) — %v\n",
        ac.Value(), goroutines*increments, time.Since(atomicTime))

    // 2. Mutex counter
    mc := &MutexCounter{}
    mutexTime := time.Now()
    mutexDone := make(chan struct{})
    go func() {
        defer close(mutexDone)
        var wg sync.WaitGroup
        for g := 0; g < goroutines; g++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                for i := 0; i < increments; i++ {
                    mc.Increment()
                }
            }()
        }
        wg.Wait()
    }()
    <-mutexDone
    fmt.Printf("MutexCounter: %d (expected %d) — %v\n",
        mc.Value(), goroutines*increments, time.Since(mutexTime))

    // 3. Channel counter
    cc := NewChannelCounter()
    channelTime := time.Now()
    channelDone := make(chan struct{})
    go func() {
        defer close(channelDone)
        var wg sync.WaitGroup
        for g := 0; g < goroutines; g++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                for i := 0; i < increments; i++ {
                    cc.Increment()
                }
            }()
        }
        wg.Wait()
        cc.Close()
    }()
    <-channelDone
    fmt.Printf("ChannelCounter: %d (expected %d) — %v\n",
        cc.Value(), goroutines*increments, time.Since(channelTime))

    fmt.Println("\nConclusion: Atomic > Mutex > Channel (performance from high to low)")
}
100%
sequenceDiagram
    participant G1 as Goroutine 1
    participant G2 as Goroutine 2
    participant G3 as Goroutine 3
    participant M as Mutex
    
    G1->>M: Lock()
    Note over G1,M: G1 holds the lock
    G2->>M: Lock() blocked
    G3->>M: Lock() blocked
    G1->>M: Unlock()
    M-->>G2: wake up
    M-->>G3: still blocked
    G2->>M: Lock() success
    Note over G2,M: G2 holds the lock
    G2->>M: Unlock()
    M-->>G3: wake up
    G3->>M: Lock() success
🔥 خطأ شائع: لا يمكن نسخ sync.Mutex. إذا قمت بتمرير بنية تحتوي على Mutex بالقيمة (بدلاً من المؤشر)، فسيتم نسخ حالة Mutex — مما يؤدي إلى سلوك غير متوقع في إلغاء القفل. قم دائمًا بتمريرها عبر المؤشر (*Counter).



❓ أسئلة شائعة

س كيف أختار بين Mutex و RWMutex؟
ج إذا كانت عمليات القراءة تفوق عمليات الكتابة بكثير (>10:1) → RWMutex؛ وإذا كانت عمليات القراءة والكتابة متساوية تقريبًا أو كانت عمليات الكتابة تفوق عمليات القراءة → Mutex. يسمح RLock الخاص بـ RWMutex بعمليات القراءة المتزامنة، لكن القفل نفسه يجعل جميع عمليات القراءة والكتابة متبادلة. وسيؤدي اختيار النوع الخاطئ إلى انخفاض الأداء.
س كيف أستخدم sync.Once؟
ج يضمن sync.Once.Do(f) أن يتم تنفيذ f مرة واحدة فقط، حتى لو استدعته 1,000 goroutine في وقت واحد. الاستخدامات النموذجية: تكوينات التحميل المتأخر، ونمط singleton، والموارد التي تحتاج إلى التهيئة مرة واحدة فقط. ويتم تنفيذه داخليًا باستخدام العمليات الذرية وموتكس.
س في أي الحالات يكون sync.Map مناسبًا؟
ج توصي الوثائق الرسمية بحالتين: (1) عندما يتم كتابة المفتاح مرة واحدة فقط ولكن يتم قراءته عدة مرات (التخزين المؤقت)؛ (2) عندما تقوم عدة goroutines بالقراءة أو الكتابة أو التكرار على مجموعات مختلفة من المفاتيح. بخلاف ذلك، فإن استخدام خريطة مع RWMutex يكون أبسط ويوفر أداءً مماثلاً.
س في أي الحالات يُعد atomic مناسبًا؟
ج العدادات البسيطة، وعلامات الحالة، والقفل المتفائل CAS (المقارنة والتبديل). atomic أسرع بـ 10 إلى 100 مرة من الموتكس، ولكنه لا يمكن استخدامه إلا مع الأنواع الأساسية مثل int وuint وpointer. بالنسبة للمنطق المعقد، استخدم الموتكس بدلاً من ذلك.
س كيف أستخدم أداة الكشف عن التضارب في الوصول إلى البيانات؟
ج go run -race main.go أو go test -race ./.... تكتشف هذه الأداة حالات التضارب في الوصول إلى البيانات أثناء وقت التشغيل — فإذا قامت اثنتان من goroutines بالوصول في وقت واحد إلى نفس المتغير وكانت إحدى العمليتين على الأقل عملية كتابة، فسيتم إصدار تحذير. يُنصح بإبقائها مفعّلة في بيئة CI/CD.
س متى ينبغي استخدام sync.Pool؟
ج عندما تحتاج إلى تخصيص وإلغاء تخصيص الكائنات المؤقتة بشكل متكرر (مثل المخازن المؤقتة لفك تشفير JSON أو رسائل Protobuf). يقوم المجمع بتخزين الكائنات المخصصة مؤقتًا لتقليل العبء على أداة جمع القمامة. ملاحظة: قد تستعيد أداة جمع القمامة الكائنات الموجودة في المجمع في أي وقت.
س هل Mutex قابل لإعادة الدخول؟
ج لا. sync.Mutex في لغة Go هو قفل غير قابل لإعادة الدخول — إذا استدعت نفس goroutine Lock مرتين متتاليتين، فسيؤدي ذلك إلى حدوث تعطل متبادل. إذا كنت بحاجة إلى الحصول على القفل مرة أخرى داخل نفس goroutine، فقم بإعادة هيكلة الكود لاستخدام sync.Mutex مع الاستدعاءات التكرارية، أو استخدم defer لضمان تحرير القفل بشكل صحيح.
س هل ترتيب استخدام Lock و RLock مهم؟
ج إنه مهم للغاية. لا يسمح RWMutex باستدعاء Lock أثناء وجود RLock — فهذا سيؤدي إلى حدوث تعطل متبادل. كما أنه لا يسمح باستدعاء RLock أثناء وجود Lock (الأولوية للكاتب). التوصية: استخدم Lock فقط لعمليات الكتابة وRLock فقط لعمليات القراءة؛ ولا تخلط بينهما.

📖 ملخص


📝 تمارين

  1. المشكلة الأساسية (الصعوبة ⭐): استخدم موتكس لحماية عمليات القراءة والكتابة المتزامنة على map[string]int. قم بتشغيل 10 غوروتينات كاتبة لكتابة البيانات و10 غوروتينات قارئة لقراءة البيانات، وتأكد من عدم وجود تضارب في التنافس (اجتياز اختبار -race).

  2. مشكلة متقدمة (درجة الصعوبة ⭐⭐): قارن أداء أربعة أنواع من العدادات (الذري، وMutex، وRWMutex، والقناة) من خلال قياس الوقت المستغرق والتحقق من صحة 100 goroutine تقوم بتنفيذ 10,000 عملية زيادة. استخدم -race للتحقق من عدم وجود تضارب في الوصول إلى البيانات.

  3. التحدي (الصعوبة ⭐⭐⭐): تنفيذ ذاكرة تخزين مؤقتة ذات تزامن عالٍ تتضمن عمليات قراءة كثيرة وعمليات كتابة قليلة. المتطلبات: (1) استخدام RWMutex للحماية؛ (2) دعم عمليات Get/Set/Delete/Range؛ (3) دعم انتهاء صلاحية TTL (time.Now().After(expireAt))؛ (4) حظر جميع عمليات القراءة أثناء عمليات الكتابة، مع السماح بعمليات القراءة المتزامنة؛ (5) اجتياز اختبار -race.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%