היכרות עם תכנות מרובה חוטים בלזרוס

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

פעילות מרובת חוטים, תלויה בהרבה מאוד גורמים, כדוגמת, תמיכת חומרה (מעבד בד"כ), תמיכה במערכת ההפעלה, לפעמים תמיכה במהדר עצמו (תמיכה בשפה עצמה).

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

במקרה של לזרוס, הוא מיישם רמה גובהה יותר של ניהול חוטים, כאשר הוא מנסה לתת כמה שפחות דגש על שינויים במערכות ההפעלה השונות, דבר שיצר לביצוע מעט שונה מאשר הביצוע שקיים בדלפי, אשר מבוסס על מערכת הפעלה ספציפית -> Microsoft Windows.

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

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

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

program ThreadExample;
{$mode objfpc}{$H+}

uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Interfaces, // this includes the LCL widgetset
Forms
{ you can add units after this }, untThreadTest;
...

כמו שנית לראות יש IFDEF עבור מערכות יוניקס (לינוקס בניהם), מפעיל מנהל חוטים. טוב למעשה כאן צריך לבצע או פעולה של define (בד"כ נעביר אותה למהדר עצמו באמצעות לזרוס), או שנוריד את {IFDEF UseCThreads}.

בהגדרות המהדר באמצעות לזרוס אנחנו נשתמש בתפריט, נבחר ב Project ושם נבחר ב Compiler Options. שם אנחנו נבחר בלשונית Other ובשדה Custom Options, אנחנו נכתוב את האפשרות

-dUseCThreads

האפשרות הזו בעצם אומרת למהדר להגדיר define גלובלי של UseCThreads, אשר ישפיע על התוכנית שלנו.

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

type
TMyThreadExample = class(TThread)
protected
procedure Execute; override; // We are overriding the default execution code
end;

ועכשיו בחלק ה implementation נממש את החוט שיצרנו (בלזרוס נשתמש בקיצור CTRL+SHIFT+C בשביל שלזרוס יצור עבורנו את ההגדרה בתוך חלק ה implementation):

procedure TMyThreadExample.Execute;
begin
// Do not inherit here any code !!
Do Something
end;

הדוגמה הזו כמובן פשוטה מאוד, ויותר חשוב אינה נועלת את הקוד/שינויים שלנו, כך שבמקביל יכלו עוד חוטים לשנות את אותו הקוד.

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

TMyThreadExample = class(TThread)
private
procedure RealExecute;
protected
procedure Execute; override; // We are overriding the default execution code
end;

ובחלק הביצוע, נעשה דבר כזה:

procedure TMyThreadExample.RealExecute;
begin
Do something
end;
procedure TMyThreadExample.Execute;
begin
// Do not inherit here any code !!
Synchronize(@RealExecute);
end;

וזהו, נעלנו את הקוד שלנו לשינויים.

זה סוף ההסבר ההתחלתי לעבודה עם חוטים בלזרוס.

6 מחשבות על “היכרות עם תכנות מרובה חוטים בלזרוס

  1. שי

    לא, לא נעלת את הקוד לשינויים — גרמת לכך ש-Execute לא יוכל להתבצע מספר פעמים בו זמנית. התכוונת, כנראה, לכך שנעלת את הנתונים ש-RealExecute משתמש בהם, אבל אלא אם Synchronize עושה אוואנטות בלתי-סבירות בעליל, אפילו זה לא נכון (אם אני אוסיף בתוך ה-class פרוצדורה בשם Exec2 שתיגע באותם נתונים, היא תוכל להתבצע במקביל עם RealExecute, נכון? ואפילו אם הפרוצדורה שלי תסונכרן באותה הדרך, אני מנחש).

    החוטים, הרי, לא התכוונו לשנות לך את הקוד, נכון? מה שכתבת דומה לסיפור הזה:
    http://thedailywtf.com/Articles/Privately-Public.aspx

  2. ik_5 מאת

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

    אני אקרא שוב את מה שכתבתי ואולי אשנה את הניסוח.

  3. ארתיום

    יש לך אולי קישור למדריך בנושא Threads ב־Pascal?

    סתם לדוגמה דברים שמעניינים אותי:
    מימוש של read/write locks, מימוש של conditional variables וממשק של semaphores?

    האם יש הגדרות שלהם כמשותפים בין תהליכים שונים (process shared mutex)?

    יש לך הפניה? סתם מעניין אותי לראות עד כמה שזה משקף את כל היכולות של POSIX Threads.

  4. ik_5 מאת

    הדבר הכי קרוב למדריך שאני מכיר הוא:
    http://wiki.lazarus.freepascal.org/Multithreaded_Application_Tutorial

    לצערי אין מדריך בכלל שאני מכיר לנושא.

    שים לב שבתחביר של FPC, ישנה הגדרה למשל של threadvar, אשר גורם למשתנים גלובליים להיות שהם יחודיים לכל thread וככה כל thread מכיל משתנה משל עצמו, למרות שזה משתנה גלובלי (בפונקציות ופרוצדורות, זו בכל מקרה ההתנהגות).
    אתה מוזמן לראות גם את הדבר הבא:
    http://www.freepascal.org/docs-html/prog/progch10.html#x212-21500010

    וכאן אתה מוזמן לחפש את הנהול threads הבסיסיים ביותר (חפש את המילה thread בשביל למצוא את הפונקציות):
    http://www.freepascal.org/docs-html/rtl/system/index-5.html

  5. ארתיום

    מס' הערות: ב־Pascal הם די לקחו את המודל של Win32API קרי Events+CriticalSection.

    הבעיה עם Win32API שהוא די מוגבל מהרבה בחינות: למשל, לא ניתן ליצור mutex בזכרון משותף, אין דבר כזה rwlock (אם כי ניתן לממש אותו בעזרת Events+CS) ועוד.

    האם ב־Pascal יש bindings ל־pthreads?

כתיבת תגובה

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

הלוגו של WordPress.com

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

תמונת Twitter

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

תמונת Facebook

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

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

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

מתחבר ל-%s