בדיקת קוד רב-הברגה בג'אווה

1. הקדמה

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

נבין גם כיצד נוכל לפתור חלק מהבעיות הללו ולבדוק קוד מרובה הליכי ביעילות ב- Java.

2. תכנות במקביל

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

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

2.1. חוטים ותכנות מקביל

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

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

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

2.2. קושי בבדיקת תוכניות מקבילות

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

עיקר ה בעיה הקשורה לתכנות במקביל נובעת משימוש בשרשורים מקוריים עם זיכרון משותף. בדיקת תוכניות כאלה קשה מאותן סיבות. שרשורים מרובים עם גישה לזיכרון משותף דורשים בדרך כלל הכללה הדדית. בדרך כלל אנו משיגים זאת באמצעות מנגנון שמירה כלשהו באמצעות מנעולים.

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

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

2.3. אנטומיה של שזירת חוטים

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

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

דלפק אינטליגנטי פרטי; תוספת חלל ציבורית () {counter ++; }

עכשיו ל הגדלת מספר שלם פרימיטיבי בג'אווה אינה פעולה אטומית. זה מורכב מקריאת הערך, הגדלתו ולבסוף שמירתו. בעוד ששרשורים מרובים מבצעים את אותה הפעולה, היא עשויה להוליד השתלבות אפשרית רבות:

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

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

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

3. בדיקת קוד רב-הברגה

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

נתחיל בהגדרת כיתה פשוטה השומרת על ספירת כל דבר:

מחלקה ציבורית MyCounter {ספירת מידע פרטית; תוספת חלל ציבורית () {int temp = count; ספירה = temp + 1; } // גטר לספירה}

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

3.1. בדיקת חלקים שאינם מקבילים

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

@Test ציבורי מבטל testCounter () {מונה MyCounter = MyCounter חדש (); עבור (int i = 0; i <500; i ++) {counter.increment (); } assertEquals (500, counter.getCount ()); }

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

3.2. ניסיון ראשון לבדיקה במקביל

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

@Test מבטל בטל @CounterWithConcurrency () זורק InterruptedException {int numberOfThreads = 10; שירות ExecutorService = Executors.newFixedThreadPool (10); תפס CountDownLatch = CountDownLatch חדש (numberOfThreads); מונה MyCounter = MyCounter חדש (); עבור (int i = 0; i {counter.increment (); latch.countDown ();}); } latch.await (); assertEquals (numberOfThreads, counter.getCount ()); }

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

3.3. ניסיון טוב יותר לבדיקה במקביל

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

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

תוספת ריקות מסונכרנת ציבורית () זורקת InterruptedException {int temp = count; חכה (100); ספירה = temp + 1; }

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

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

בחלק מאוחר יותר נראה כיצד נוכל לעשות זאת מבלי לשנות את הקוד.

עכשיו, בואו ונבדוק באופן דומה את הקוד הזה כמו שעשינו קודם:

@Test מבטל בטל ציבוריSummationWithConcurrency () זורק InterruptedException {int numberOfThreads = 2; שירות ExecutorService = Executors.newFixedThreadPool (10); תפס CountDownLatch = CountDownLatch חדש (numberOfThreads); מונה MyCounter = MyCounter חדש (); עבור (int i = 0; i {נסה {counter.increment ();} לתפוס (InterruptedException e) {// Handle exception} latch.countDown ();}); } latch.await (); assertEquals (numberOfThreads, counter.getCount ()); }

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

4. כלי בדיקה זמינים

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

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

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

4.1. tempus-fugit

ספריית ג'אווה של tempus-fugit עוזר לנו לכתוב ולבדוק קוד מקביל בקלות. אנו נתמקד רק בחלק הבדיקה בספרייה זו כאן. ראינו קודם כי הפקת לחץ על קוד עם מספר אשכולות מגדילה את הסיכויים למצוא פגמים הקשורים במקביל.

בעוד שאנחנו יכולים לכתוב כלי עזר כדי לייצר את הלחץ בעצמנו, tempus-fugit מספק דרכים נוחות להשיג את אותו הדבר.

בואו ונבקר שוב באותו קוד שניסינו לייצר מתח לפני כן ונבין כיצד נוכל להשיג אותו באמצעות tempus-fugit:

מחלקה ציבורית MyCounterTests {@ כלל ציבורי ConcurrentRule ציבורי במקביל = ConcurrentRule חדש (); כלל כלל @ RepeatingRule כלל = כלל RepeatingRule חדש); מונה MyCounter סטטי פרטי = MyCounter חדש (); @Test @Concurrent (count = 10) @Repeating (repetition = 10) פועל חלל פומביMultipleTimes () {counter.increment (); } @ AfterClass חלל סטטי ציבורי annotatedTestRunsMultipleTimes () זורק InterruptedException {assertEquals (counter.getCount (), 100); }}

כאן אנו משתמשים בשניים מ- כְּלָלזה זמין לנו מ tempus-fugit. כללים אלה מיירטים את המבחנים ועוזרים לנו ליישם את ההתנהגויות הרצויות, כמו חזרה ובמקביל. לכן, למעשה, אנו חוזרים על הפעולה הנבדקת עשר פעמים כל אחת מתוך עשרה שרשורים שונים.

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

4.2. חוט אורג

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

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

בואו נראה איך נוכל לשפר את הניסיון הקודם והנאיבי שלנו:

MyCounterTests בכיתה ציבורית {דלפק MyCounter פרטי; @ThreadedBefede לפני הריקון הציבורי לפני () {counter = MyCounter חדש (); } @ThreadedMain ציבורי ריק ריק mainThread () {counter.increment (); } @ThreadedSecondary ציבור ריק secondThread () {counter.increment (); } @ThreadedAfter בטל פומבי אחרי () {assertEquals (2, counter.getCount ()); } @Test מבטל בטל ציבורי () {new AnnotatedTestRunner (). RunTests (this.getClass (), MyCounter.class); }}

הנה, הגדרנו שני שרשורים שמנסים להגדיל את הדלפק שלנו. Thread Weaver ינסה להריץ את הבדיקה הזו עם האשכולות הללו בכל תרחישי התערובת האפשריים. יתכן שבאחד מהמשתלבים נקבל את הפגם, וזה די ברור בקוד שלנו.

4.3. MultithreadedTC

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

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

בואו נראה כיצד נוכל לבדוק את הקוד שלנו בצורה יותר דטרמיניסטית עם MultithreadedTC:

MyTests בכיתה ציבורית מרחיב את MultithreadedTestCase {מונה MyCounter הפרטי; @Override חלל ציבורי מאותחל () {counter = MyCounter חדש (); } פתיל הריק 1 () זורק את InterruptedException {counter.increment (); } thread2 ריק (ריק) ציבורי זורק את InterruptedException {counter.increment (); } @ Override public void finish () {assertEquals (2, counter.getCount ()); } @Test ציבורי בטל testCounter () זורק Throwable {TestFramework.runManyTimes (MyTests חדשים (), 1000); }}

כאן אנו מגדירים שני שרשורים להפעלת הדלפק המשותף ולהגדלתו. הגדרנו את MultithreadedTC לבצע בדיקה זו באמצעות האשכולות הללו עבור עד אלף השתלבות שונה עד שהיא תזהה כישלון שנכשל.

4.4. ג'אווה jcstress

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

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

@JCStressTest @Outcome (id = "1", expect = ACCEPTABLE_INTERESTING, desc = "עדכון אחד אבד.") @Outcome (id = "2", expect = ACCEPTABLE, desc = "שני העדכונים.") @State הציבורי של המדינה MyCounterTests {דלפק פרטי של MyCounter; @Actor הציבור הריק שחקן 1 () {counter.increment (); } @Actor הציבור הריק שחקן 2 () {counter.increment (); } @ Arbiter פוסק חלל ציבורי (I_Result r) {r.r1 = counter.getCount (); }}

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

לבסוף, יש לנו שיטה המסומנת בהערה פּוֹסֵק, שלמעשה מבקר את המדינה רק פעם אחת שַׂחְקָןהם ביקרו בו. השתמשנו גם בהערות תוֹצָאָה להגדיר את הציפיות שלנו.

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

5. דרכים אחרות לאיתור בעיות במקביל

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

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

5.1. ניתוח סטטי

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

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

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

כלי ניתוח סטטי נפוץ עבור Java הוא FindBugs. FindBugs מחפש מקרים של "דפוסי באגים". תבנית באגים היא שפת קוד שהיא לעיתים קרובות שגיאה. זה עשוי לנבוע מכמה סיבות כמו מאפייני שפה קשים, שיטות לא מובנות, וסטרים לא מובנים.

FindBugs בודק את קוד הביץ 'של Java לאיתור המופעים של דפוסי באגים מבלי לבצע את קוד הביץ 'בפועל. זה די נוח לשימוש ומהיר להפעלה. FindBugs מדווח על באגים השייכים לקטגוריות רבות כמו תנאים, עיצוב וקוד כפול.

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

5.2. בדיקת מודל

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

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

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

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

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

6. מחשבות אחר

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

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

6.1. הפחת את המורכבות

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

כעת, אף שאלה אינם עוסקים ישירות בנושא כתיבת מבחנים לקוד מקביל, הם מקלים על הניסיון לבצע את העבודה.

6.2. שקול פעולות אטומיות

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

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

6.3. חבק את אי-התחלפות

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

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

6.4. הימנע מזיכרון משותף

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

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

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

7. מסקנה

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

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

קוד המקור של מאמר זה ניתן למצוא באתר GitHub.


$config[zx-auto] not found$config[zx-overlay] not found