בעיות ש-multithreading פותר ב-Java
בעיקרו של דבר, Java multithreading הומצא כדי לפתור שתי בעיות עיקריות:-
בצע מספר פעולות בו זמנית.
בדוגמה למעלה, חוטים שונים (כלומר בני משפחה) ביצעו מספר פעולות במקביל: שטפו כלים, הלכו לחנות, קיפלו דברים.
ניתן לתת דוגמה "מתכנת" יותר. תאר לעצמך שיש לך תוכנית עם ממשק משתמש. כאשר לוחצים על הלחצן המשך, כמה חישובים צריכים להתרחש בתוך התוכנית, והמשתמש אמור לראות את מסך הממשק הבא. אם פעולות אלה מבוצעות ברצף, לאחר לחיצה על כפתור "המשך", התוכנית פשוט תקפא. המשתמש יראה את אותו מסך עם כפתור "המשך" עד להשלמת כל החישובים הפנימיים והתוכנית תגיע לחלק בו יתחיל לצייר את הממשק.
ובכן, בוא נחכה כמה דקות!
אנחנו יכולים גם ליצור מחדש את התוכנית שלנו, או, כפי שאומרים מתכנתים, "לעשות במקביל". אפשר לבצע את החישובים הדרושים בשרשור אחד, ולעבד ממשק באחר. לרוב המחשבים יש מספיק משאבים לכך. במקרה זה, התוכנית לא תהיה "טיפשה", והמשתמש יעבור ברוגע בין מסכי הממשק מבלי לדאוג למה שקורה בפנים. זה לא מפריע :)
-
להאיץ את החישובים.
הכל הרבה יותר פשוט כאן. אם למעבד שלנו יש כמה ליבות, ורוב המעבדים הם כעת מרובי ליבות, ניתן לפתור את רשימת המשימות שלנו במקביל על ידי מספר ליבות. ברור שאם נצטרך לפתור 1000 בעיות וכל אחת מהן תיפתר בשנייה, ליבה אחת תתמודד עם הרשימה תוך 1000 שניות, שתי ליבות תוך 500 שניות, שלוש תוך קצת יותר מ-333 שניות, וכן הלאה.
Thread
. כלומר, כדי ליצור ולהפעיל 10 שרשורים, תזדקק ל-10 אובייקטים מהמחלקה הזו. נכתוב את הדוגמה הפשוטה ביותר:
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("I'm Thread! My name is " + getName());
}
}
כדי ליצור ולהפעיל שרשורים, עלינו ליצור מחלקה ולרשת אותה מה- java.lang
. Thread
ולעקוף את השיטה בו run()
. האחרון חשוב מאוד. בשיטה run()
אנו קובעים את ההיגיון שהשרשור שלנו חייב לבצע. כעת, אם ניצור מופע MyFirstThread
ונפעיל אותו, השיטה run()
תדפיס קו עם השם שלה לקונסולה: השיטה getName()
מדפיסה את שם ה"מערכת" של השרשור, המוקצה אוטומטית. למרות שבעצם, למה "אם"? בואו ליצור ולבדוק!
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
פלט מסוף: אני שרשור! שמי הוא Thread-2 I'm Thread! שמי הוא שרשור-1 אני חוט! שמי הוא Thread-0 I'm Thread! שמי הוא Thread-3 I'm Thread! שמי הוא Thread-6 I'm Thread! שמי הוא Thread-7 I'm Thread! שמי הוא Thread-4 I'm Thread! שמי הוא Thread-5 I'm Thread! שמי הוא Thread-9 I'm Thread! שמי Thread-8 אנו יוצרים 10 שרשורים (אובייקטים) MyFirstThread
שיורשים מהם Thread
ומשיקים אותם על ידי קריאה לשיטת האובייקט start()
. לאחר קריאה למתודה , start()
השיטה שלה מתחילה לעבוד run()
, והלוגיקה שנכתבה בה מבוצעת. שימו לב: שמות השרשור אינם מסודרים. זה די מוזר, למה הם לא הוצאו להורג בתורם: Thread-0
, Thread-1
, Thread-2
וכן הלאה? זו בדיוק דוגמה לכך שחשיבה סטנדרטית, "רציפה" לא תעבוד. העובדה היא שבמקרה זה אנו מוציאים רק פקודות כדי ליצור ולהפעיל 10 שרשורים. באיזה סדר יש להשיק אותם קובע מתזמן השרשור: מנגנון מיוחד בתוך מערכת ההפעלה. איך זה בדיוק בנוי ועל פי איזה עיקרון הוא מקבל החלטות זה נושא מאוד מורכב, ולא נצלול אליו עכשיו. הדבר העיקרי שיש לזכור הוא שהמתכנת לא יכול לשלוט ברצף ביצוע השרשור. כדי להבין את חומרת המצב, נסה להפעיל את השיטה main()
מהדוגמה למעלה עוד כמה פעמים. פלט קונסולה שני: I'm Thread! שמי הוא Thread-0 I'm Thread! שמי הוא Thread-4 I'm Thread! שמי הוא Thread-3 I'm Thread! שמי הוא Thread-2 I'm Thread! שמי הוא שרשור-1 אני חוט! שמי הוא Thread-5 I'm Thread! שמי הוא Thread-6 I'm Thread! שמי הוא Thread-8 I'm Thread! שמי הוא Thread-9 I'm Thread! שמי הוא Thread-7 פלט קונסולה שלישית: I'm Thread! שמי הוא Thread-0 I'm Thread! שמי הוא Thread-3 I'm Thread! שמי הוא שרשור-1 אני חוט! שמי הוא Thread-2 I'm Thread! שמי הוא Thread-6 I'm Thread! שמי הוא Thread-4 I'm Thread! שמי הוא Thread-9 I'm Thread! שמי הוא Thread-5 I'm Thread! שמי הוא Thread-7 I'm Thread! שמי הוא Thread-8
בעיות ש-multithreading יוצר
בדוגמה עם ספרים, ראית ש-multithreading פותר בעיות חשובות למדי, והשימוש בו מאיץ את עבודת התוכניות שלנו. במקרים רבים - פעמים רבות. אבל לא בכדי ריבוי השרשורים נחשב לנושא מורכב. אחרי הכל, אם משתמשים בו בצורה לא נכונה, זה יוצר בעיות במקום לפתור אותן. כשאני אומר "צור בעיות", אני לא מתכוון למשהו מופשט. ישנן שתי בעיות ספציפיות ש-multithreading יכול לגרום: מבוי סתום ומצב גזע. מבוי סתום הוא מצב שבו שרשורים מרובים ממתינים למשאבים שנכבשו זה על ידי זה, ואף אחד מהם לא יכול להמשיך בביצוע. נדבר על זה יותר בהרצאות עתידיות, אבל לעת עתה הדוגמה הזו תספיק: תארו לעצמכם ש-thread-1 עובד עם אובייקט-1 כלשהו, ו-thread-2 עובד עם אובייקט-2. התוכנית כתובה כך:- Thread-1 יפסיק לעבוד עם Object-1 ויעבור ל-Object-2 ברגע ש-Thread-2 יפסיק לעבוד עם Object 2 ויעבור ל-Object-1.
- Thread-2 יפסיק לעבוד עם Object-2 ויעבור ל-Object-1 ברגע ש-Thread-1 יפסיק לעבוד עם Object 1 ויעבור ל-Object-2.
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("Выполнен поток " + getName());
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
עכשיו דמיינו שהתוכנית אחראית לתפעול של רובוט שמכין אוכל! חוט-0 מוציא את הביצים מהמקרר. זרם 1 מדליק את הכיריים. Stream-2 מוציא מחבת ומניח אותה על הכיריים. זרם 3 מדליק אש על הכיריים. זרם 4 שופך שמן למחבת. זרם 5 שובר את הביצים ושופך אותן לטיגון. זרם 6 זורק את הפגזים לפח האשפה. Stream-7 מסיר את הביצים הטרופות המוגמרות מהאש. פוטוק-8 מניח ביצים מקושקשות בצלחת. Stream-9 שוטף כלים. תסתכל על התוצאות של התוכנית שלנו: שרשור-0 הוצא לפועל שרשור-2 שרשור הוצא לפועל חוט-1 שרשור הוצא לפועל חוט-4 שרשור הוצא לפועל חוט-5 שרשור הוצא לפועל שרשור-8 שרשור הוצא לפועל אשכול-7 שרשור הופעל -3 שרשור-6 הוצא לפועל. האם התסריט מהנה? :) והכל כי פעולת התוכנית שלנו תלויה בסדר ביצוע השרשורים. בהפרה הקלה ביותר של הרצף, המטבח שלנו הופך לגיהנום, ורובוט שהשתגע הורס את כל מה שמסביבו. זו גם בעיה נפוצה בתכנות מרובה-תשריט, עליה תשמעו יותר מפעם אחת. בסיום ההרצאה ברצוני להמליץ לכם על ספר על ריבוי חוטים.
GO TO FULL VERSION