ארכיון

רשומות עם התג ‘locking’

Safe Value Pattern

23 יוני, 2010 2 תגובות

בפוסט הזה: קוד קצר ולעניין של משתנה שקוראים אותו ומעדכנים אותו בסביבה שהיא Multi-Threaded.

כידוע, כדי להגן על המשתנה מפני corruption אנחנו צריכים נעילה (כן, אני יודע, יש גם את Interlock, אבל זה לא העניין פה, אל תטרידו אותי עם עובדות…)

אז במקום להצהיר בכל פעם על משתנה ועל האובייקט שנועל אותו, הנה Mini-Pattern שעושה את העבודה:

הגירסה הפשוטה

מנעול פשוט, עבודה עם lock:

namespace Pepperoni.Lib
{
  public class SafeValue<T>
  {
    private readonly object locker = new object();
    private T innerValue;

    public SafeValue()
    {
    }

    public SafeValue(T initialValue)
    {
      innerValue = initialValue;
    }

    public T Value
    {
      get
      {
        lock (locker)
        {
          return innerValue;
        }
      }
      set
      {
        lock (locker)
        {
          innerValue = value;
        }
      }
    }
  }
}

אין הרבה מה לכתוב ב code-review, בסה"כ גם ב getter וגם ב setter נועלים אובייקט פנימי ורק אז מחזירים את הערך (ב getter) או מעדכנים את הערך (ב setter). קצר, פשוט, קריא, עובד (נראה לי שאני מאמץ את זה בראשי תיבות: קפק"ע! :-P).

אלטרנטיבה – הרבה קריאות, מעט עדכונים

אם אנחנו יודעים שיש הרבה יותר קריאות של הערך לעומת עדכונים של הערך, כלומר הרבה יותר שימושים ב getter מאשר ב setter, אז נוכל לייעל את העבודה אם נשתמש באובייקט ReaderWriterLockSlim (לקריאה נוספת – MSDN). הנה הקוד (עודכן לפי ההצעה של חן אגוזי):

using System.Threading;

namespace Pepperoni.Lib
{
  public class SafeValueSeldomWrites<T>
  {
    private T innerValue;
    private readonly ReaderWriterLockSlim locker = new ReaderWriterLockSlim();
    
    public SafeValueSeldomWrites()
    {
    }

    public SafeValueSeldomWrites(T initialValue)
    {
      innerValue = initialValue;
    }

    public T Value
    {
      get
      {
        locker.EnterReadLock();
        try
        {
          T result = innerValue;        
          return result;
        }
        finally
        {
          locker.ExitReadLock();
        }
      }
      set
      {
        locker.EnterWriteLock();
        try
        {
          innerValue = value;
        }
        finally
        {
          locker.ExitWriteLock();
        }
      }
    }
  }
}

גם כאן הקוד יחסית פשוט וקריא, הנעילה מתבצעת עם ה ReaderWriterLockSlim שמוצהר כ private. רק לא לשכוח ש finally תמיד-תמיד מתבצע, גם אם יש return בתוך ה try שלו.

עוד כמה מילים

אם רוצים, אז אפשר לעשות כאן אבסטרקציה לשני המימושים (ע"י interface או base-class). אבל זה נראה לי מיותר, כי בד"כ יודעים כבר בזמן הפיתוח עצמו באיזה מימוש להשתמש.

עניין נוסף: במקרים מסויימים אפשר לעבוד עם Interlock, שמאפשר קריאה/כתיבה בסביבה שהיא Multi-Threaded, ללא שימוש ב lock הסטנדרטי. מומלץ לגגל או לקרוא ב MSDN.

אפשר להוריד מכאן את הקוד, למי ש copy-paste גדול עליו 🙂
תכנות נעים!

קטגוריות:תכנות תגיות:, ,

Locking by Decorator

בפוסט הזה:

  • הקדמה
  • הגדרת החוזה
  • מימוש רגיל ללא נעילות
  • נעילה באמצעות Decorator
  • יישום

הקדמה

לא מזמן כתבתי פוסט שמציע להפריד בין לוגיקה של רכיב ללוגיקה של נעילה ע"י אבסטרקציה. בפוסט הזה אני מציע דרך נוספת לעטוף רכיב בנעילה, תוך שימוש ב Design Pattern שנקרא Decorator. המטרה היא שוב להפריד בין לוגיקת רכיב ללוגיקת נעילה, כדי ליצור קוד נקי יותר וגמיש יותר לפי צרכי הפרויקט.

הגדרת החוזה

כדי לא להסתבך יותר מדי, אני אמשיך את הרעיון מהפוסט ההוא, ולכן דוגמת ה Counter תלווה אותנו גם כאן. נמיר את ה Counter שלנו מ class ל interface באופן הבא:

public interface ICounter
{
    void Hit();
    int Current { get; }
}

מימוש רגיל ללא נעילות

המימוש הרגיל והמיידי ל interface הנ"ל:

public class Counter : ICounter
{
    private int counter = 0;

    public void Hit()
    {
        ++counter;
    }

    public int Current
    {
        get { return counter; }
    }        
}

עד כאן טוב ויפה, אבל המימוש הוא כמובן לא thread-safe. עם זאת, הלוגיקה שבמימוש הזה ברורה וקריאה, ונוכל לכתוב unit tests שמתייחסים למימוש הזה בצורה פשוטה ומהירה. זה יתרון חשוב.

נעילה באמצעות Decorator

קצת הקדמה: מה זה Decorator?

אז ככה: בגדול, Decorator זה Design Pattern שמתייחס ל interface-ים ולהוספת פונקציונליות בזמן ריצה, לא על ידי ירושה. אפשר לקרוא עוד בויקיפדיה. במקרה שלפנינו, נוסיף פונקציונליות של נעילה על המימוש של ICounter. או, בניסוח אחר: "נקשט" את הפונקציונליות של Counter בנעילה, אבל נשמור על ה interface. כלומר החוזה נשמר אבל נקבל עוד משהו במימושים.

ניגש לקוד עצמו של ה Decorator שלנו:

public class CounterLockDecorator : ICounter
{
    private readonly ICounter core;
    private readonly object syncObject = new object();

    public CounterLockDecorator(ICounter coreCounter)
    {
        if (null == coreCounter)
            throw new ArgumentNullException("coreCounter");

        core = coreCounter;
    }

    public void Hit()
    {
        lock (syncObject)
        {
            core.Hit();
        }
    }

    public int Current
    {
        get 
        {
            lock (syncObject)
            {
                return core.Current;
            }
        }
    }
}

כפי שניתן לראות, ה Decorator מממש בעצמו את החוזה ICounter. אבל הוא לא עושה את כל העבודה – הוא מצפה לקבל בקונסטרקטור שלו מימוש קיים ל ICounter ו"מגלגל" אליו את הקריאות לפי הצורך. כך, למשל, במימוש של המתודה Hit: ה Decorator רק עוטף בנעילה את הקריאה למתודת ה Hit של המימוש הקיים שהוא מחזיק.

יישום

הקריאה ל Counter הרגיל, ללא נעילות, יכולה להתבצע כך:

ICounter myCounter = new Counter();

ואם רוצים את הפונקציונליות של הנעילות, נוכל לקבל ICounter באופן הבא:

ICounter myCounter = new CounterLockDecorator(new Counter());

סיכום

גם בפוסט הזה הראיתי כיצד ניתן להפריד בין הלוגיקה של הרכיב לבין הנעילות שלו. יש להפרדה הזו גם יתרונות וגם חסרונות. היתרונות העיקריים הם קוד גמיש וטסטבילי (כלומר כזה שניתן לבדיקות בצורה קלה). גם הקוד של הרכיב עצמו קריא וברור. עם זאת, יש אי אילו חסרונות להפרדה הזו: פתאום יש שלושה קבצים לתחזק (החוזה, המימוש הרגיל וה decorator) במקום אחד, והקריאה הופכת להיות קצת יותר מורכבת (אפשר לפתור את זה עם factory). יש פתרון נוסף לעניין הזה, והוא שימוש ב dynamic proxy. אם יהיה לי זמן אני ארחיב על זה, אבל הרעיון הכללי הוא ליצור את ה Decorator בזמן ריצה באמצעות הכלים הסטנדרטיים של דוט נט כמו CodeDOM או באמצעות פריימוורק שהוא קצת יותר high level כמו Dynamic Proxy של Castle.

אגב, אפשר להשתמש בנעילה האבסטרקטית שהצעתי בפוסט ההוא בתוך ה Decorator וכך "להרוויח" עוד קצת גמישות בבחירת מנגנון הנעילה עצמו, אבל זה כבר עניין אחר 🙂

כל הקוד נמצא כאן, ושיהיה קישוט נעים!

Quantcast