GPars – Agent & Data Flow

בפוסט  האחרון בסדרת המקביליות נראה שני עוד שני מודלים לחישוב מקבילי על קצה המזלג.

Agent

המודל של העברת הודעות בין תהליכים ללא זיכרון משותף תורם, כפי שראינו, לכתיבה של מערכות מקביליות בצורה פשוטה ובטוחה, אבל יש מקרים שבהם זיכרון משותף דווקא יכול לעזור. מטמון (cache) של אפליקציה הוא דוגמא שרמזתי עליה בסוף הפוסט הקודם. הנה המימוש של זה במודל של Agent:

 

import groovyx.gpars.agent.Agent

agent = new Agent([])

agent.send {it.add 1}

agent.send {it.add 2}

sleep(1000)

println agent.val

 

הקוד הזה ממחיש כמה מהעקרונות של Agent:

בשורה הראשונה יוצרים Agent שמכיל רשימה ריקה. למעשה Agent יכול לעטוף אוביקטים מכל סוג ולהוסיף להם יכולות.

בשורה השניה והשלישית מוסיפים ערכים לרשימה על ידי שליחה של קוד לביצוע (Closure). הAgent אחראי לבצע את הקוד על ידי Thread משלו.

בשורה הרביעית מחכים. הוספתי את ההמתנה הזו כדי להדגיש את העובדה שהAgent יבצע את ההוספות בזמנו החופשי, ואין ביטחון שההוספה תתבצע מיד אחרי שהיא מופיעה.

בשורה החמישית רואים את ההשפעה של מה שהוספנו.

המימוש של Agent בGPars מתבצע על ידי שימוש בActor (זה לא מחויב המציאות. בשפת clojure שממנה בא הרעיון, Agent הוא מבנה בשפה ולא מימוש מעל משהו אחר) אבל ההבדל העיקרי הוא שבמקום לשלוח הודעות שולחים ממש קוד לביצוע, ובעצם מעבירים את השליטה לאוביקט שמנהל את המידע. זה קצת מזכיר את עקרון ההכמסה. מי שנתקל פעם ראשונה בקוד כזה

 

Public void setX(int x){

                this.x=x;

}

 

בטוח שזה קוד מיותר – למה לקרוא למטודה במקום פשוט לקבוע את הערך של x מבחוץ? אבל בעצם זה קוד חשוב מאוד. הוא מעביר את השליטה במידע לאוביקט שמנהל אותו. באופן דומה Agent עושה הכמסה לדרך בה הקוד מתבצע – שולחים אליו בקשות אבל הוא זה ששולט בסדר ובזמן הביצוע שלהן.

נקודה נוספת שחשוב לשים לב אליה היא הגמישות הרבה שמקבל הAPI ברגע שניתן לשלוח closure כארגומנט. נראה עוד דוגמא:

 

class Point{

                int x,y

}

agent2 = new Agent(new Point())

agent2.send {it.x=5;it.y=7}

sleep 1000

println agent2.val.y

 

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

Data flow

הדוגמא האחרונה שנראה בקצרה היא של מודל חישובי בשם Data Flow. המודל נשען על ההנחות הבאות:

  1. ניתן לחלק עיבוד גדול למספר תתי עיבודים.
  2. הקלטים של תת עיבוד מסוים הם הפלטים של תתי עיבודים אחרים.
  3. במהלך העיבוד כל ערך מחושב פעם אחת בלבד (במילים אחרות, כל תת עיבוד ירוץ פעם אחת בלבד).

ההנחות האלה בעצם מאפשרות קוד שמחכה לחישוב של הקלטים שנחוצים לו (ההמתנה לא חייבת לנבוע מסיבוכיות החישוב. יכול להיות מצב שמחכים לנתונים מרוחקים או אפילו לקלט מהמשתמש), ולמעשה "מסדרת" את תתי החישובים בסדר הנכון, בלי קשר לסדר בו הם התחילו לרוץ. לשם המחשה נסתכל על הפסאודו קוד הבא:

 

Calculate{

If (! a.hasValue) a.wait()

If (! b.hasValue) b.wait()

Return a+b

}

setA(value){

                a=value

                a.notify()

}

setB(value){

                b=value

                b.notify()

}

 

בדוגמא הזו, לא משנה באיזה סדר קוראים לפעולות, הן תמיד 'יתארגנו' בסופו של דבר לרוץ בסדר הנכון. בנוסף, כל 'תת-חישוב' יכול לרוץ באופן עצמאי מאחר והוא תלוי רק בנתוני הקלט שלו (אמנם יש זיכרון משותף, אבל העובדה שניתן לקבוע את ערכו רק פעם אחת הופכת אותו לבטוח).

העבודה עם DataFlow דומה. הנה דוגמא בסיסית ביותר:

 

import static groovyx.gpars.dataflow.Dataflow.task

import groovyx.gpars.dataflow.DataflowVariable

final def x = new DataflowVariable()

final def y = new DataflowVariable()

final def z = new DataflowVariable()

task {

    z << x.val + y.val

}

task {

    x << 10

}

task {

    y << 5

}

println "Result: ${z.val}"

 

כמובן שהסינטקס פשוט וכל פעולות ההמתנה והאיתות נעשות מתחת למכסה המנוע. כמו כן קיים הבדל חשוב נוסף – כמו במודלים הקודמים אין מיפוי של תתי חישובים לThreads של מערכת ההפעלה, אלא התשתית משתמשת בThread pool קיים ומתזמנת את ההקצאות שלו למשימות שצריכות לרוץ. זה מאפשר יצירה של המון 'תהליכונים' מאחר והם קלים מאוד יחסית לנימים של מערכת הפעלה. בדף של GPars ניתן לראות עוד דוגמאות רבות ליכולות של Data Flow ולשימושים מתקדמים שניתן לממש.

סיכום

בפוסטים האחרונים ראינו את החשיבות של תכנות מקבילי בעידן הענן והמעבדים מרובי הליבה.הכרנו את GPars, ספרייה בשפת groovy לתכנות מקבילי, והזכרנו חלק מהמודלים התכנותיים בנושא. כדי לשמור על קריאות לפעמים החומר הוצג בצורה פשטנית או מקוצרת ומומלץ להתעמק למי שזה רלוונטי. הנה כמה לינקים שימושיים:

GPars דף התיעוד הראשי של הסיפרייה. רוב דוגמאות הקוד נילקחו מכתובת זו או מבוססות עליה.

AKKA ספרייה עשירה בשפת JAVA (למעשה scala) לשימוש בActors

Erlang שפה דינאמית שנוצרה עבור תכנות מקבילי ומבוזר ומציעה הרבה מבנים בשפה (ובספרייה הראשית שלה OTP) שמאפשרים יצירה קלה של מערכות מבוזרות אמינות ביותר. אני אישית מאוד נהנה ללמוד את השפה הזו במיוחד בגלל שהגישה שונה מאוד משפות כמו ג'אווה וזה פותח את הראש לחשיבה אחרת לחלוטין.

כתיבת תגובה

הזינו את פרטיכם בטופס, או לחצו על אחד מהאייקונים כדי להשתמש בחשבון קיים:

הלוגו של WordPress.com

אתה מגיב באמצעות חשבון WordPress.com שלך. לצאת מהמערכת / לשנות )

תמונת Twitter

אתה מגיב באמצעות חשבון Twitter שלך. לצאת מהמערכת / לשנות )

תמונת Facebook

אתה מגיב באמצעות חשבון Facebook שלך. לצאת מהמערכת / לשנות )

תמונת גוגל פלוס

אתה מגיב באמצעות חשבון Google+ שלך. לצאת מהמערכת / לשנות )

מתחבר ל-%s