Menu
CoddyTech
flag Ar iconالعربيةdown icon

sync.Mutex و sync.RWMutex

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

مع أن القنوات هي الطريقة المفضلة في Go لتنسيق عمل goroutines، فإنك تحتاج أحيانًا إلى حماية البيانات المشتركة مباشرةً. توفّر الحزمة sync كائنات المزامنة: وهي أقفال تضمن وصول 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 تخزّن كميات المنتجات في خريطة وتستخدم sync.RWMutex لحماية الوصول. يجب أن يدعم نظام الجرد العمليات التالية:

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

    تذكّر استخدام 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 عبر الإنترنت