المكدس بلا أقفال لا يحجب أبدًا لكنه لا يستطيع ببساطة تحرير عقدة أخرجها: ربما قرأ خيط آخر الرأس قبل لحظة وهو على وشك قراءة next لتلك العقدة. الذاكرة المحرَّرة قد يُعاد استعمالها أو تُلغى فتكون تلك القراءة استعمالًا بعد التحرير. هناك علاجان كلاسيكيان. مؤشرات الخطر تتيح للخيط أن يعلن العقدة التي يستعملها. والعصور تتيح له أن يعلن متى دخل. وفي كليهما لا تُحرَّر العقدة المفصولة إلا حين لا يستطيع أحد إمساكها.
التحرير الفوري: السحب غير الآمن
النموذج هو مكدس موضوع ABA. يقرأ الخيط الرأس ثم العقدة ثم يبدّل الرأس بعملية compare-and-swap واحدة. يحرّر السحب غير الآمن العقدة في الدور نفسه الذي ينجح فيه التبديل. العقدة المحرَّرة لا يجوز لأحد لمسها وتسجّل read_node استعمالًا بعد التحرير كلما قرأ خيط واحدة منها. خيطان يسحب كل منهما مرة يعطيان 20 جدولًا فقط ويجد المستكشف السيئة فورًا: تلك التي قرأ فيها خيط الرأس وسحب الآخر العقدة وحرّرها ثم يقرؤها الأول.
def pop_unsafe(shared, tid): while True: yield idx = shared["head"] if idx == -1: return None yield nxt, value = read_node(shared, idx) yield if compare_and_swap(shared, "head", idx, nxt): shared["popped"] = shared["popped"] + (value,) free_node(shared, idx) return valuedef two_pops(shared): return [pop_unsafe(shared, 0), pop_unsafe(shared, 1)]outcomes = explore_all(two_pops, make_heap([9369, 9305]))hits = sum(n for key, n in outcomes.items() if dict(key)["uaf"])print(sum(outcomes.values()), "schedules,", hits, "of them read a freed node")
20 schedules, 6 of them read a freed node
مؤشرات الخطر: أعلن ثم تحقق
يحوّل مؤشر الخطر التحرير إلى حوار. قبل قراءة عقدة يكتب الخيط رقمها في خانته. وهذا وحده لا يكفي لأن العقدة قد تكون سُحبت وحُرّرت بين قراءة الرأس وكتابة الخانة فيقوم الخيط بـالتحقق: يقرأ الرأس مرة أخرى ويبدأ من جديد إن تحرك. وبعد سحب ناجح لا يحرّر الخيط العقدة بل يضعها في قائمته الخاصة بالمتقاعدة ويمسح: يقرأ خانة كل خيط مرة واحدة ويحرّر فقط العقد المتقاعدة التي لا تسميها خانة. وتنتظر العقد المتبقية مسحًا لاحقًا.
def pop_hazard(shared, tid): while True: yield idx = shared["head"] if idx == -1: shared["hp%d" % tid] = -1 return None yield shared["hp%d" % tid] = idx yield if shared["head"] != idx: continue yield nxt, value = read_node(shared, idx) yield if compare_and_swap(shared, "head", idx, nxt): shared["popped"] = shared["popped"] + (value,) shared["hp%d" % tid] = -1 shared["retired%d" % tid] = shared["retired%d" % tid] + (idx,) break yield hazards = tuple(shared["hp%d" % t] for t in range(shared["threads"])) yield keep = [] for node in shared["retired%d" % tid]: if node in hazards: keep.append(node) else: free_node(shared, node) shared["retired%d" % tid] = tuple(keep) return valuedef two_pops(shared): return [pop_hazard(shared, 0), pop_hazard(shared, 1)]outcomes = explore_all(two_pops, make_heap([9369, 9305]))hits = sum(n for key, n in outcomes.items() if dict(key)["uaf"])waiting = max(sum(len(dict(key)["retired%d" % t]) for t in (0, 1)) for key in outcomes)print(sum(outcomes.values()), "schedules,", hits, "read a freed node, up to", waiting, "node waiting at the end")
12122 schedules, 0 read a freed node, up to 1 node waiting at the end
إعلان لكل خيط عن العقدة الوحيدة التي تُقرأ، يُكتب قبل القراءة ويُتحقَّق منه بعدها. لا تُحرَّر العقدة المتقاعدة إلا إذا لم يسمّها أي إعلان.
العصور: أعلن متى دخلت
يعلن استرجاع العصور أقل بكثير. هناك عدّاد عصر عام واحد. يكتب الخيط العصر الحالي في خانته عند دخول عملية ويكتب -1 عند الخروج ولا يعلن عقدًا بعينها أبدًا. العقدة المتقاعدة في العصر e لا يمسكها إلا خيط دخل في العصر e أو قبله فيأمن تحريرها حين يعلن كل خيط داخل عملية عصرًا أكبر من e. الثمن أن خيطًا يبقى طويلًا بالداخل يحبس كل العقد المتقاعدة منذ دخوله بينما يحبس مؤشر الخطر العقدة التي يسميها فقط.
def pop_epoch(shared, tid): yield shared["ep%d" % tid] = shared["epoch"] while True: yield idx = shared["head"] if idx == -1: shared["ep%d" % tid] = -1 return None yield nxt, value = read_node(shared, idx) yield if compare_and_swap(shared, "head", idx, nxt): shared["popped"] = shared["popped"] + (value,) shared["ep%d" % tid] = -1 shared["retired%d" % tid] = shared["retired%d" % tid] + ((idx, shared["epoch"]),) shared["epoch"] = shared["epoch"] + 1 break announced = [] for t in range(shared["threads"]): yield if shared["ep%d" % t] != -1: announced.append(shared["ep%d" % t]) yield keep = [] for node, epoch in shared["retired%d" % tid]: if announced and epoch >= min(announced): keep.append((node, epoch)) else: free_node(shared, node) shared["retired%d" % tid] = tuple(keep) return valuedef two_pops(shared): return [pop_epoch(shared, 0), pop_epoch(shared, 1)]outcomes = explore_all(two_pops, make_heap([9369, 9305]))hits = sum(n for key, n in outcomes.items() if dict(key)["uaf"])waiting = max(sum(len(dict(key)["retired%d" % t]) for t in (0, 1)) for key in outcomes)print(sum(outcomes.values()), "schedules,", hits, "read a freed node, up to", waiting, "node waiting at the end")
9012 schedules, 0 read a freed node, up to 1 node waiting at the end
يترك الأسلوبان عقدًا متقاعدة في الانتظار في نهاية بعض الجداول: يبلّغ المستكشف عنها بوصفها pending. يحرّرها مسح لاحق فلا يضيع شيء. الخطر الحقيقي للعصور خيط يتوقف داخل عملية: لا يعلن عصرًا أحدث أبدًا فلا تُحرَّر أي عقدة متقاعدة بعده وتنمو الذاكرة. ومؤشرات الخطر محصّنة لأن الخيط الواقف يحبس عقدة واحدة فقط.
السلامة تكلّف جداول لأن كل إعلان وتحقق ومسح خطوة إضافية يستطيع الخيط الآخر التسلل إليها: صار 20 إلى 12122 و9012. ولهذا بالضبط يفيد المستكشف هنا. فهو لا يحتاج أن يعرف لماذا الأسلوب صحيح بل يفحص كل جدول من تلك الجداول.
ما ستبنيه
يتبع المختبر الدرس: الحالة المشتركة بمجموعة خانات لكل خيط والسحب غير الآمن والسحب بمؤشرات الخطر والسحب بالعصور وفحص الثابتين (لا استعمال بعد التحرير ولا شيء مفقود) والمدقق الذي يستكشف كل جداول أسلوب والدراسة للأساليب الثلاثة على إغلاقات SPY.
- الدالة · السؤالتعتمد على
- make_heap(values, threads)ما المشترك؟حساب بسيط
- pop_unsafe(shared, tid)اسحب وحرّر فورًاحساب بسيط
- pop_hazard(shared, tid)أعلن وتحقق وأحِل للتقاعد وامسححساب بسيط
- pop_epoch(shared, tid)ادخل وأحِل للتقاعد بالعصر واسترجعحساب بسيط
- violations(final)أي الثوابت انكسرت؟حساب بسيط
- verify(scheme, values, pops)هل الأسلوب آمن تحت كل الجداول؟explore_allviolations
- study(bars)أي أسلوب ينجو في مكدس SPY؟verify
الموضوع التالي: كان هذا مكدسًا ولعبة صغيرة. المشروع الختامي توزيع بيانات سوق بلا أقفال يشغّل المهارة كلها: تتدفق نقرات SPY من منتج واحد إلى عدة مستهلكين عبر بنية بلا أقفال ويثبت المستكشف ثوابتها تحت كل الجداول.
المختبر: ما الذي يُقيَّم
7 مهام · 100 نقطة- الحالة المشتركة5 نقطة
- التحرير الفوري10 نقطة
- مؤشرات الخطر20 نقطة
- العصور20 نقطة
- الثوابت10 نقطة
- المدقق15 نقطة
- الدراسة على جدول SPY20 نقطة