8.10 - ביטול עבודה חיצונית וסגירת callbacks - פתרון
פתרון תקופת השקט¶
פתרון ראשון - ספירה של כניסות¶
אפשר להחליף את הפונקציה הראשית בניסוי הבא ולהשאיר את הטיפוסים מההרצאה. כל עובד מקבל ערוץ שחרור משלו, ולכן סדר הסיום נשלט.
fn main() {
let calls = Arc::new(AtomicUsize::new(0));
let context = Arc::new(Context {
active: Mutex::new(0), quiet: Condvar::new(), calls: calls.clone(),
});
let weak = Arc::downgrade(&context);
let provider = Arc::new(Provider { registration: Mutex::new(Some(context)) });
let mut releases = Vec::new();
let mut workers = Vec::new();
for _ in 0..2 {
let (entered, entrance) = mpsc::channel();
let (release, released) = mpsc::channel();
let source = provider.clone();
workers.push(std::thread::spawn(move || {
let entry = source.enter().unwrap();
entered.send(()).unwrap();
released.recv().unwrap();
entry.0.calls.fetch_add(1, Ordering::SeqCst);
}));
entrance.recv().unwrap();
releases.push(release);
}
let (removed, removal) = mpsc::channel();
let (finished, finish) = mpsc::channel();
let closing = provider.clone();
let closer = std::thread::spawn(move || {
closing.close(removed);
finished.send(()).unwrap();
});
removal.recv().unwrap();
releases.remove(0).send(()).unwrap();
workers.remove(0).join().unwrap();
assert!(matches!(finish.try_recv(), Err(mpsc::TryRecvError::Empty)));
assert!(weak.upgrade().is_some());
releases.remove(0).send(()).unwrap();
workers.remove(0).join().unwrap();
finish.recv().unwrap();
closer.join().unwrap();
assert!(weak.upgrade().is_none());
assert_eq!(calls.load(Ordering::SeqCst), 2);
}
הצטרפות לעובד הראשון מבטיחה שהכניסה הראשונה כבר הסתיימה. העובד השני עדיין מחכה בערוץ, ולכן המונה חייב להישאר חיובי. כך הבדיקה אינה מסתפקת בכך ששתי פעולות סיום קרו בסופו של דבר, אלא בודקת את מצב הביניים המסוכן.
פתרון שני - סגירה כמצב משותף¶
נדרש מצב משותף שמבחין בין פתוח, נסגר וסגור. הקריאה הראשונה מעבירה מפתוח לנסגר ומונעת כניסות חדשות. כל קריאה שמוצאת נסגר מחכה על אותו תנאי עד שהמצב הופך לסגור. רק הגורם שמוודא שמספר הפעילים אפס מסמן סגור ומעיר את כל הממתינים.
אפשר להחזיק את מצב הסגירה ואת מספר הפעילים תחת אותו מנעול, ולשמור את ההקשר עד המעבר הסופי. במימוש מההרצאה הקריאה הראשונה מוציאה את ההקשר מתוך הרישום. קריאה שנייה יכולה למצוא None ולחזור בזמן שהראשונה עדיין מחכה, ולכן היעדר רישום אינו אישור שקט מלא.
פתרון שלישי - חוזה הכניסה חשוב מהמונה¶
צריך לדעת כיצד מזוהות כל הכניסות שכבר התחילו, ומתי כל אחת מפסיקה להשתמש בהקשר. אם הספק יכול להיכנס לפונקציה לפני שנרשמה בעלות מוגנת, העטיפה אינה יכולה להוכיח בטיחות באמצעות מונה פנימי בלבד. נדרש מנגנון הצטרפות של הספק, יחידת בעלות לכל כניסה או חוזה אחר שמכסה גם את חלון הזמן שלפני הגדלת המונה.
סגירה מתוך פונקציית החזרה צריכה בדרך כלל להיות בקשת עצירה שאינה מחכה לעצמה. גורם חיצוני מבצע לאחר מכן את ההצטרפות ואת שחרור ההקשר. אם הממשק מתיר סגירה מלאה מתוך פונקציית החזרה, חייב להיות לו חוזה מפורש שמסביר כיצד נמנעת המתנה לכניסה הנוכחית וכיצד נשמרים המשאבים עד יציאתה.