מבוא לפסי נעילה

1. הקדמה

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

2. הבעיה

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

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

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

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

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

3. פסי נעילה

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

ישנן מספר דרכים לעשות זאת:

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

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

4. דוגמה מהירה

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

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

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

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

4.1. תלות

מכיוון שאנחנו הולכים להשתמש בגויאבה מְפוּספָּס בכיתה, נוסיף את גויאבה תלות:

 com.google.guava גויאבה 28.2-jre 

4.2. תהליך ראשי

שֶׁלָנוּ ConcurrentAccessExperiment מחלקה מיישמת את ההתנהגות שתוארה בעבר:

מחלקה מופשטת ציבורית ConcurrentAccessExperiment {גמר ציבורי מפה doWork (מפת מפה, שרשור int, חריצי int) {CompletableFuture [] בקשות = חדש CompletableFuture [חריצי * חריצים]; עבור (int i = 0; i <threads; i ++) {בקשות [slots * i + 0] = CompletableFuture.supplyAsync (putSupplier (מפה, i)); בקשות [slots * i + 1] = CompletableFuture.supplyAsync (getSupplier (מפה, i)); בקשות [slots * i + 2] = CompletableFuture.supplyAsync (getSupplier (מפה, i)); בקשות [משבצות * i + 3] = CompletableFuture.supplyAsync (getSupplier (מפה, i)); } CompletableFuture.allOf (בקשות) .join (); מפת חזרה; } putSupplier מוגן מופשט מוגן (מפת מפה, מקש int); ספק מוגן ספק מופשט (מפת מפה, מפתח int); }

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

4.3. גישה מקבילה עם ReentrantLock

כעת ניישם את השיטות למשימות האסינכרוניות שלנו.

שֶׁלָנוּ סינגל לוק class מגדיר נעילה אחת לכל מבנה הנתונים באמצעות a ReentrantLock:

מחלקה ציבורית SingleLock מרחיב את ConcurrentAccessExperiment {ReentrantLock lock; SingleLock ציבורי () {lock = reentrantLock חדש (); } מוגן על ידי ספק putSupplier (מפה מפה, מקש int) {return (() -> {lock.lock (); נסה {return map.put ("מקש" + מקש, "ערך" + מקש);} לבסוף {lock. לבטל נעילה(); } }); } getSupplier של ספק מוגן (מפת מפה, מקש int) {return (() -> {lock.lock (); נסה {return map.get ("key" + key);} לבסוף {lock.unlock ();}} ); }}

4.4. גישה מקבילה עם מְפוּספָּס

אז ה StripLock מחלקה מגדירה נעילת פסים לכל דלי:

מחלקה ציבורית StripedLock מרחיב את ConcurrentAccessExperiment {נעילת פסים; פסי StripLock (דליים אינט) {lock = Striped.lock (דליים); } putSupplier מוגן של ספק (מפה מפה, מקש int) {return (() -> {int bucket = key% stripedLock.size (); lock lock = stripedLock.get (bucket); lock.lock (); נסה {להחזיר מפה .put ("מקש" + מקש, "ערך" + מקש);} לבסוף {lock.unlock ();}}); } getSupplier של ספק מוגן (מפת מפה, מקש int) {return (() -> {int bucket = key% stripedLock.size (); Lock lock = stripedLock.get (bucket); lock.lock (); try {return map .get ("מקש" + מקש);} סוף סוף {lock.unlock ();}}); }}

אז איזו אסטרטגיה מתפקדת טוב יותר?

5. תוצאות

בואו נשתמש ב- JMH (רתמת Java Microbenchmark). ניתן למצוא את אמות המידה דרך קישור קוד המקור בסוף ההדרכה.

בהפעלת המדד שלנו אנו יכולים לראות משהו הדומה לדברים הבאים (שימו לב שתפוקה גבוהה יותר טובה יותר):

יחידות מידה Cnt ציון שגיאה יחידות ConcurrentAccessBenchmark.singleLockConcurrentHashMap thrpt 10 0,059 ± 0,006 ops / ms ConcurrentAccessBenchmark.singleLockHashMap thrpt 10 0,061 ± 0,005 ops / ms ConcurrentAccessBenchmark.stripedLockConcurrentHashMap thrpt9 0,0 

6. מסקנות

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

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

כרגיל, קוד המקור של הדרכה זו זמין ב- GitHub.


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