בעיות נפתרות על ידי ריבוי פתילים
ריבוי ההליכים הומצא למעשה כדי להשיג שתי מטרות חשובות:-
עשה כמה דברים במקביל.
בדוגמה למעלה, חוטים שונים (בני משפחה) ביצעו מספר פעולות במקביל: הם שטפו כלים, הלכו לחנות וארזו דברים.
אנו יכולים להציע דוגמה הקשורה יותר לתכנות. נניח שיש לך תוכנית עם ממשק משתמש. כאשר אתה לוחץ על 'המשך' בתוכנית, כמה חישובים אמורים להתרחש והמשתמש אמור לראות את המסך הבא. אם פעולות אלו בוצעו ברצף, התוכנית פשוט תתקע לאחר שהמשתמש ילחץ על כפתור 'המשך'. המשתמש יראה את המסך עם מסך כפתור 'המשך' עד שהתוכנה תבצע את כל החישובים הפנימיים ותגיע לחלק בו ממשק המשתמש מתרענן.
ובכן, אני מניח שנחכה כמה דקות!
או שנוכל לעבד את התוכנית שלנו מחדש, או, כפי שאומרים מתכנתים, 'לעשות במקביל' אותה. בואו נערוך את החישובים שלנו על שרשור אחד ונצייר את ממשק המשתמש על אחר. לרוב המחשבים יש מספיק משאבים לעשות זאת. אם נלך בדרך זו, התוכנית לא תקפא והמשתמש יעבור בצורה חלקה בין המסכים מבלי לדאוג למה שקורה בפנים. אחד לא מפריע לשני :)
-
בצע חישובים מהר יותר.
הכל הרבה יותר פשוט כאן. אם למעבד שלנו יש מספר ליבות, ולרוב המעבדים כיום יש, אז כמה ליבות יכולות להתמודד עם רשימת המשימות שלנו במקביל. ברור שאם אנחנו צריכים לבצע 1000 משימות וכל אחת לוקחת שנייה אחת, ליבה אחת יכולה לסיים את הרשימה תוך 1000 שניות, שתי ליבות תוך 500 שניות, שלוש בקצת יותר מ-333 שניות וכו'.
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("I'm Thread! My name is " + getName());
}
}
כדי ליצור ולהריץ שרשורים, עלינו ליצור מחלקה, לגרום לה לרשת את ה- java.lang . מחלקת שרשור , ודרוס את שיטת run() שלה . הדרישה האחרונה חשובה מאוד. בשיטת run() אנו מגדירים את ההיגיון לביצוע השרשור שלנו. כעת, אם ניצור ונפעיל מופע של MyFirstThread , השיטה run() תציג שורה עם שם: השיטה getName() מציגה את שם ה-'system' של השרשור, המוקצה אוטומטית. אבל למה אנחנו מדברים בהיסוס? בואו ליצור אחד ולגלות!
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 objects, שיורשים את Thread ) ונתחיל אותם על ידי קריאה למתודה start() בכל אובייקט. לאחר קריאה למתודה start() , ההיגיון בשיטת run() מבוצע. הערה: שמות השרשור אינם מסודרים. זה מוזר שהם לא היו ברצף: Thread-0 , Thread-1 , Thread-2 , וכן הלאה? כפי שזה קורה, זו דוגמה לתקופה שבה חשיבה 'רציפה' אינה מתאימה. הבעיה היא שסיפקנו רק פקודות ליצירה והרצה של 10 שרשורים. מתזמן השרשור, מנגנון מיוחד של מערכת הפעלה, מחליט את סדר הביצוע שלהם. העיצוב המדויק שלו ואסטרטגיית קבלת ההחלטות הם נושאים לדיון עמוק שלא נצלול אליו כרגע. הדבר העיקרי שיש לזכור הוא שהמתכנת לא יכול לשלוט בסדר הביצוע של שרשורים. כדי להבין את חומרת המצב, נסה להפעיל את שיטת main() בדוגמה למעלה עוד כמה פעמים. פלט מסוף בריצה שנייה: אני שרשור! שמי הוא 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 Console פלט מהריצה השלישית: 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 יכול ליצור: מבוי סתום ותנאי מירוץ. מבוי סתום הוא מצב שבו שרשורים מרובים ממתינים למשאבים המוחזקים זה בזה, ואף אחד מהם לא יכול להמשיך לרוץ. נדבר על זה יותר בשיעורים הבאים. הדוגמה הבאה תספיק לעת עתה:
- Thread-1 מפסיק את האינטראקציה עם Object-1 ועובר ל-Object-2 ברגע ש-Thread-2 מפסיק את האינטראקציה עם Object-2 ועובר ל-Object-1.
- Thread-2 מפסיק את האינטראקציה עם אובייקט-2 ועובר ל-Object-1 ברגע ש-Thread-1 מפסיק את האינטראקציה עם Object-1 ועובר ל-Object-2.
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("Thread executed: " + 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 מדליק את הכיריים. חוט-2 מקבל מחבת ומניח אותו על הכיריים. חוט-3 מדליק את הכיריים. חוט-4 יוצק שמן למחבת. חוט-5 שובר את הביצים ויוצק אותן למחבת. חוט-6 זורק את קליפות הביצים לפח האשפה. חוט-7 מוציא את הביצים המבושלות מהמבער. חוט-8 מניח את הביצים המבושלות על צלחת. חוט-9 שוטף את הכלים. תסתכל על התוצאות של התוכנית שלנו: שרשור הוצא לפועל: שרשור-0 שרשור הוצא: שרשור-2 התבצע שרשור-1 שרשור הוצא לפועל: שרשור-4 הוצא לפועל: שרשור-9 שרשור הופעל: שרשור-5 השרשור הופעל: אשכול-8 הוצא להורג: שרשור-7 שרשור הוצא להורג: שרשור-3 האם זו שגרת קומדיה? :) והכל בגלל שעבודת התוכנית שלנו תלויה בסדר הביצוע של השרשורים. בהתחשב בהפרה הקטנה ביותר של הרצף הנדרש, המטבח שלנו הופך לגיהנום, ורובוט מטורף הורס את כל מה שמסביבו. זו גם בעיה נפוצה בתכנות מרובי-הליכים. אתה תשמע על זה יותר מפעם אחת. בסיום שיעור זה, ברצוני להמליץ על ספר על ריבוי שרשורים. 
GO TO FULL VERSION