לדלג לתוכן

פתרון מבנה עבודה וסיום שיתופי

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

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

מבנה חיים אפשרי

מבנה השדות יכול להיראות כך; הקטע הוא תרשים מבנה למחלקת העובד, ולא תוכנית עצמאית:

class Worker {
    std::mutex mutex_;
    std::condition_variable_any changed_;
    std::deque<int> queue_;
    bool accepting_ = true;
    bool aborting_ = false;
    std::jthread thread_; // Destroyed first, before shared state.
};

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

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

בדיקה שמכוונת לתזמון

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

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

המעגל שנמנע

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

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