Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

sync.Mutex و sync.RWMutex

جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 63 من 107.

رغم أن القنوات هي الطريقة المفضلة في Go لتنسيق goroutines، فإنك تحتاج أحيانًا إلى حماية البيانات المشتركة مباشرةً. توفّر الحزمة sync كائنات mutex: أقفالًا تضمن وصول goroutine واحدة فقط إلى مورد ما في كل مرة.

يمتلك sync.Mutex طريقتين: Lock() وUnlock(). عندما يستدعي goroutine الدالة Lock()، يحصل على وصول حصري. ستتوقف goroutines الأخرى التي تستدعي Lock() حتى يتم استدعاء Unlock():

type Counter struct {
    mu    sync.Mutex
    value int
}

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

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

يضمن استخدام defer c.mu.Unlock() تحرير القفل حتى إذا أعادت الدالة القيمة مبكرًا أو حدثت حالة هلع: وهو نمط شائع وموصى به.

عندما تكون عمليات القراءة متكررة ولكن عمليات الكتابة نادرة، يوفّر sync.RWMutex أداءً أفضل. فهو يسمح لعدة قرّاء بالوصول في الوقت نفسه، لكن الكتّاب يحصلون على وصول حصري:

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

func (c *Cache) Get(key string) string {
    c.mu.RLock()         // يُسمح بعدة قراء
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // وصول حصري للكتابة
    defer c.mu.Unlock()
    c.data[key] = value
}

استخدم RLock()/RUnlock() لعمليات القراءة وLock()/Unlock() لعمليات الكتابة. يتيح ذلك إجراء عمليات قراءة متزامنة مع ضمان أمان عمليات الكتابة.

challenge icon

التحدي

سهل

لنَبْنِ نظام جرد آمنًا للخيوط يتتبع مستويات مخزون المنتجات. سيتعامل نظامك بأمان مع عمليات القراءة والكتابة المتزامنة باستخدام الأقفال، مما يضمن سلامة البيانات عند حدوث عمليات متعددة في الوقت نفسه.

ستنظم شيفرتك عبر ملفين:

  • inventory.go: عرّف نظام إدارة جرد آمنًا للخيوط.

    أنشئ بنية Inventory تخزّن كميات المنتجات في map وتستخدم sync.RWMutex لحماية الوصول. يجب أن يدعم جردك العمليات التالية:

    • NewInventory() *Inventory - تنشئ جردًا جديدًا باستخدام map مهيأة
    • AddStock(product string, quantity int) - تضيف الكمية إلى مخزون المنتج (استخدم قفلًا حصريًا لأن هذه العملية تعدّل data)
    • GetStock(product string) int - تعيد المخزون current لمنتج، أو 0 إذا لم يكن found (استخدم قفل القراءة لأن هذه العملية لا تقرأ إلا data)
    • RemoveStock(product string, quantity int) bool - تزيل الكمية من المخزون إذا كان مخزون كافٍ exists. تعيد true إذا نجحت، وfalse إذا كان المخزون insufficient (استخدم قفلًا حصريًا)

    تذكّر استخدام defer لإلغاء القفل، لضمان تحرير الأقفال دائمًا بطريقة صحيحة.

  • main.go: اقرأ العمليات ووضّح عمل جردك الآمن للخيوط.

    اقرأ عدد العمليات، ثم عالج كل عملية. لكل عملية نوع (add أو get أو remove)، واسم منتج، وبالنسبة إلى عمليات add/remove، كمية.

    لكل عملية، اطبع النتيجة:

    • add: اطبع Added [quantity] [product]
    • get: اطبع [product]: [stock] in stock
    • remove: اطبع Removed [quantity] [product] إذا نجحت، أو Insufficient stock for [product] إذا لم تنجح

سيتم توفير المدخلات التالية:

  • السطر 1: عدد العمليات (عدد صحيح)
  • الأسطر التالية: لكل عملية:
    • نوع العملية (add أو get أو remove)
    • اسم المنتج
    • الكمية (فقط لعمليات add وremove)

على سبيل المثال، عند إعطاء:

5
add
apples
50
get
apples
remove
apples
30
remove
apples
25
get
apples

يجب أن يكون ناتجك:

Added 50 apples
apples: 50 in stock
Removed 30 apples
Insufficient stock for apples
apples: 20 in stock

يسمح RWMutex باستدعاءات متعددة لـ GetStock للقراءة في الوقت نفسه، بينما يحصل AddStock وRemoveStock على وصول حصري عند تعديل الجرد.

جرّب بنفسك

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// اقرأ عدد العمليات
	line, _ := reader.ReadString('\n')
	numOps, _ := strconv.Atoi(strings.TrimSpace(line))
	
	// أنشئ مخزونًا جديدًا
	inventory := NewInventory()
	
	// عالج كل عملية
	for i := 0; i < numOps; i++ {
		// اقرأ نوع العملية
		opLine, _ := reader.ReadString('\n')
		opType := strings.TrimSpace(opLine)
		
		// اقرأ اسم المنتج
		productLine, _ := reader.ReadString('\n')
		product := strings.TrimSpace(productLine)
		
		// TODO: تعامل مع كل نوع عملية (add, get, remove)
		// بالنسبة لـ "add" و "remove"، اقرأ الكمية من الإدخال
		// استدعِ الدالة المناسبة للمخزون
		// اطبع النتيجة وفقًا لوصف التحدي
		
		switch opType {
		case "add":
			// TODO: اقرأ الكمية، أضف المخزون، اطبع النتيجة
			
		case "get":
			// TODO: احصل على المخزون، اطبع النتيجة
			
		case "remove":
			// TODO: اقرأ الكمية، أزل المخزون، اطبع النتيجة المناسبة
			
		}
	}
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Go عبر الإنترنت