σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academyخرائط الطريقالمسارات
Ctrl K
AO
+1.2k متعلّم
تسجيل الدخولانضم ↗
خرائط الطريقالمسارات
AO
+1.2k متعلّم
تسجيل الدخول
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academyخرائط الطريقالمسارات
Ctrl K
AO
+1.2k متعلّم
تسجيل الدخولانضم ↗
خرائط الطريقالمسارات
AO
+1.2k متعلّم
تسجيل الدخول
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
مسار Lock-free›الوحدة 3 · طوابير بلا أقفال›الموضوع 3.3·~1 س 10 دمتقدم

طوابير متعددة المنتجين والمستهلكين

الموضع في المسارالوحدة 3 من 4 · الموضوع 3 من 3
3.3 طوابير متعددة المنتجين والمستهلكين
—درجة المختبر— / 100إجمالي XP—التتابع—

4 دقيقة قراءة4 أمثلة برمجية

  1. 01التذكرة ليست تسليمًا
  2. 02فحص كل الجداول
  3. 03ما الذي يشتريه رقم التسلسل
  4. 04ما ستبنيه

مع منتج واحد ومستهلك واحد يكون لكل موضع كاتب واحد. ومع الكثيرين يجب توزيع المواضع ذرّيًا ولا تكفي التذكرة: المنتج الذي يحمل الموضع 5 لم يكتب عنصره بعد. يقول رقم تسلسل على كل خانة لمن دورها. وعلى 2756 جدولًا لمنتجين و1600 لمستهلكين لا ينكسر هذا الطابور أبدًا.

01

التذكرة ليست تسليمًا

يستعمل الطابور المحدود ذو capacity خانة عدّادين لا يزيدان إلا. يأخذ المنتج الموضع التالي بمقارنة وتبديل على enq ويأخذ المستهلك التالي بمقارنة وتبديل على deq. يقع الموضع p في الخانة p % capacity. تجعل المقارنة والتبديل الحجز آمنًا: لا يحصل منتجان على الموضع نفسه. لكن حجز موضع ليس كتابة العنصر. بين التبديل والتخزين تكون الخانة محجوزة وفارغة ولو قرأها مستهلك الآن لقرأ هراءً. لذا تحمل كل خانة رقم تسلسل لا يتقدم إلا في اللحظة الصحيحة.

main.pyPython
def init_queue(capacity):    shared = {"enq": 0, "deq": 0}    for cell in range(capacity):        shared["seq%d" % cell] = cell    return shareddef enqueue(shared, item, capacity):    while True:        yield        pos = shared["enq"]        cell = pos % capacity        diff = shared["seq%d" % cell] - pos        if diff < 0:            return False        if diff > 0:            continue        yield        if compare_and_swap(shared, "enq", pos, pos + 1):            break    yield    shared["data%d" % cell] = item    shared["seq%d" % cell] = pos + 1    shared["put%d" % pos] = item    return Truedef dequeue(shared, capacity):    while True:        yield        pos = shared["deq"]        cell = pos % capacity        diff = shared["seq%d" % cell] - (pos + 1)        if diff < 0:            return None        if diff > 0:            continue        yield        if compare_and_swap(shared, "deq", pos, pos + 1):            break    yield    item = shared.get("data%d" % cell)    shared["seq%d" % cell] = pos + capacity    shared["got%d" % pos] = item    return itemsolo = lambda shared: [producer_thread((7,), 2)(shared), consumer_thread(1, 2)(shared)]for prefix in ([0, 0, 0], [0, 0, 0, 1, 1, 1], [0, 0, 1, 1, 0, 1, 1]):    print(prefix, run_prefix(solo, prefix, init_queue(2))[0])
[0, 0, 0] {'enq': 1, 'deq': 0, 'seq0': 1, 'seq1': 1, 'data0': 7, 'put0': 7}
[0, 0, 0, 1, 1, 1] {'enq': 1, 'deq': 1, 'seq0': 2, 'seq1': 1, 'data0': 7, 'put0': 7, 'got0': 7}
[0, 0, 1, 1, 0, 1, 1] {'enq': 1, 'deq': 0, 'seq0': 1, 'seq1': 1, 'data0': 7, 'put0': 7}

اقرأ التشغيلات الثلاثة. بعد [0, 0, 0] حجز المنتج الموضع 0 (enq: 1) وخزّن data0 ورفع seq0 إلى 1 فالعنصر جاهز. وبعد أدوار المستهلك الثلاثة يصير got0: 7 وseq0: 2: الخانة حرة للدورة التالية. وفي التشغيل الأخير ينظر المستهلك بين حجز المنتج ونشره فيرى seq0 = 0 بدل 1 فيمضي خالي اليدين. لا يقرأ هراءً. يقول فقط «لا شيء جاهز بعد» وستجد محاولة لاحقة العنصر.

02

فحص كل الجداول

ماذا يجب أن يعد الطابور الآن؟ يسجّل المنتجون كل عنصر تحت موضعه (put3) ويسجّل المستهلكون ما أخذوه تحت الموضع الذي حجزوه (got3). إن كان الطابور يعمل فكل موضع سُحب يحمل العنصر الذي وُضع فيه (لا mismatch) والمواضع المسحوبة هي 0 و1 و2 ... بلا ثقوب ولا تكرار (لا gap). وهذا يعطي الطابور ترتيبه: ترتيب المواضع. تقرأ violations هاتين الحقيقتين من حالة نهائية وتشغّل verify المستكشف لأي عدد من المنتجين والمستهلكين. هنا منتج واحد بعنصرين أمام مستهلكين اثنين وعنصر واحد أمام مستهلك واحد.

main.pyPython
def violations(final):    puts = {int(key[3:]): value for key, value in final.items() if key.startswith("put")}    gots = {int(key[3:]): value for key, value in final.items() if key.startswith("got")}    broken = []    if any(gots[p] != puts.get(p) for p in gots):        broken.append("mismatch")    if sorted(gots) != list(range(len(gots))):        broken.append("gap")    return sorted(broken)def verify(producers, consumers, capacity):    def make_threads(shared):        return [producer_thread(items, capacity)(shared) for items in producers] + [consumer_thread(n, capacity)(shared) for n in consumers]    outcomes = explore_all(make_threads, init_queue(capacity))    broken = {}    pushed = popped = 0    for key, count in outcomes.items():        final = dict(key)        pushed = max(pushed, sum(1 for k in final if k.startswith("put")))        popped = max(popped, sum(1 for k in final if k.startswith("got")))        for name in violations(final):            broken[name] = broken.get(name, 0) + count    return {"schedules": sum(outcomes.values()), "broken": dict(sorted(broken.items())), "pushed": pushed, "popped": popped}print(verify([(7,)], [1], 2))print(verify([(7, 8)], [1, 1], 2))
{'schedules': 4, 'broken': {}, 'pushed': 1, 'popped': 1}
{'schedules': 1600, 'broken': {}, 'pushed': 2, 'popped': 2}
تعريفرقم التسلسل

عدّاد على كل خانة يسجّل لمن الدور. لا يتغير إلا حين يُكتب العنصر (إلى p + 1) وحين يُؤخذ (إلى p + capacity) فيعرف الخيط الذي يقرؤه هل العنصر آمن الاستعمال دون أي قفل.

03

ما الذي يشتريه رقم التسلسل

لنرَ قيمته بإزالته. يحجز المستهلك المهمل أدناه موضعًا بالمقارنة والتبديل ثم يقرأ الخانة فورًا دون أن ينظر إلى seq. أمام منتج بعنصر واحد له 20 جدولًا وفي 10 منها يحجز موضعًا قبل أن ينشر المنتج شيئًا ويعيد None كعنصر أو العنصر الخطأ. أما الطابور الحقيقي فله 4 جداول للبرنامج نفسه ولا ينكسر أيٌّ منها. رقم التسلسل هو بالضبط الفحص الذي يحوّل تذكرة محجوزة إلى عنصر مسلَّم.

main.pyPython
def careless_dequeue(shared, capacity):    yield    pos = shared["deq"]    cell = pos % capacity    yield    if not compare_and_swap(shared, "deq", pos, pos + 1):        return None    yield    item = shared.get("data%d" % cell)    shared["got%d" % pos] = item    return itemdef careless_consumer(attempts, capacity):    def thread(shared):        for _ in range(attempts):            yield from careless_dequeue(shared, capacity)    return threadmake = lambda shared: [producer_thread((7,), 2)(shared), careless_consumer(1, 2)(shared)]outcomes = explore_all(make, init_queue(2))broken = sum(n for key, n in outcomes.items() if violations(dict(key)))print(sum(outcomes.values()), "schedules,", broken, "break the invariants")
20 schedules, 10 break the invariants
يجب ألا تقل السعة عن اثنين

مع خانة واحدة تكون القيمة p + 1 التي تعني «جاهزة للموضع p» هي نفسها التي تعني «حرة للموضع p + 1» وقد يكتب منتج فوق عنصر لم يُقرأ. ولهذا تقرّب التطبيقات الحقيقية السعة إلى قوة من اثنين لا تقل عن 2 وهذا يحوّل % capacity أيضًا إلى قناع رخيص.

main.pyPython
ticks = [int(round(float(c) * 100)) for c in load_bars()["close"].iloc[:2]]print(ticks)print("two producers:", verify([(ticks[0],), (ticks[1],)], [2], 2))print("two consumers:", verify([tuple(ticks)], [1, 1], 2))
[9369, 9305]
two producers: {'schedules': 2756, 'broken': {}, 'pushed': 2, 'popped': 2}
two consumers: {'schedules': 1600, 'broken': {}, 'pushed': 2, 'popped': 2}
ما تقوله البيانات الحقيقيةأول إغلاقين لـSPY (9369 و9305 سنتًا) عبر طابور من خانتين
منتجان ومستهلك2,756جدولًا، لا يكسر أيٌّ منها ثابتًا
منتج ومستهلكان1,600جدولًا، لا يكسر أيٌّ منها ثابتًا
مستهلك مهمل10من 20 جدولًا تقرأ عنصرًا غير موجود

كلا شكلي الطابور صحيح على كل الجداول والمستكشف شاهد بينما تفشل نسخة بلا فحص التسلسل في نصف فضائها (الأصغر بكثير). لا ينتظر المنتجون والمستهلكون بعضهم أبدًا: المستهلك الذي لا يجد شيئًا جاهزًا يعود ببساطة والمنتج الذي يجد الطابور ممتلئًا يعيد False. هذا هو ثمن الطابور بلا أقفال وهديته: قد تفشل العمليات لكنها لا تحجب أبدًا.

04

ما ستبنيه

يتبع المختبر الدرس: الطابور الفارغ بأرقام تسلسله وenqueue وdequeue بحجز بالمقارنة والتبديل ونشر وفحص الثابتين والمدقق الذي يشغّل المستكشف لأي عدد من المنتجين والمستهلكين ودراسة على إغلاقين لـSPY.

  • الدالة · السؤالتعتمد على
  • init_queue(capacity)كيف يبدو طابور فارغ؟حساب بسيط
  • enqueue(shared, item, capacity)كيف يحجز المنتج وينشر؟حساب بسيط
  • dequeue(shared, capacity)كيف يحجز المستهلك ويحرّر؟حساب بسيط
  • violations(final)أي الثوابت تكسرها حالة نهائية؟حساب بسيط
  • verify(producers, consumers, capacity)هل الطابور صحيح تحت كل الجداول؟explore_allviolations
  • study(bars)هل يصمد الشكلان على إغلاقات SPY؟verify

أهم الخلاصات

  • تذاكر بالمقارنة والتبديل وجاهزية بأرقام التسلسل. حجز موضع يجعله لك؛ ورقم التسلسل يخبر الجميع متى يصبح العنصر فيه جاهزًا.
  • عدم الجاهزية ليس خطأ. المستهلك الذي لا يجد شيئًا جاهزًا يعيد None ويحاول لاحقًا؛ لا ينتظر أبدًا ولا يقرأ هراءً.
  • أثبته مع إزالة رقم التسلسل كشاهد ضابط. 1600 و2756 جدولًا بلا انتهاك؛ والمستهلك المهمل ينكسر في 10 من 20.
تحقق سريع
لطابور 4 خانات. حجز مستهلك الموضع 6 وحرّر خانته. ما رقم تسلسل تلك الخانة الآن ولأي موضع هي حرة؟
اختر إجابة.

الموضوع التالي: أثبتّ الآن صحة برامج صغيرة بتشغيل كل الجداول. اختبار الشيفرة المتزامنة يقارن ذلك بالأدوات التي تستعملها الفرق الحقيقية: كاشف تسابق يراقب تشغيلًا واحدًا ويشير إلى الوصولات التي قد تتسابق، ومدقق نماذج يفعل ما يفعله مستكشفك لكن بالحيل التي تتيح له التوسع.

المختبر: ما الذي يُقيَّم

6 مهام · 100 نقطة
  • الطابور الفارغ5 نقطة
  • الإدخال25 نقطة
  • السحب25 نقطة
  • الثوابت10 نقطة
  • التحقق على كل الجداول15 نقطة
  • الدراسة على جدول SPY20 نقطة
المستكشف · lab-lf-08
▾ lab-lf-08
▾ data
…
bash · داخل المتصفح
learner@quantcraft:~/lab-lf-08$
لم يتم تحميل Python بعدPythonLn 1, Col 1Spaces: 4UTF-8

هل تريد إعادة ضبط هذا الموضوع؟

سيعود كل ما في lab-lf-08 إلى الملفات الابتدائية وستُمسح الطرفية. ستفقد حلّك الحالي ولا يمكن التراجع عن ذلك.

سيُعاد ضبطه
  • main.py ← الشيفرة الابتدائية
  • سجل الطرفية ومخرجات الاختبارات
  • التغييرات غير المُرسلة
سيُحتفظ به
  • أفضل درجة في المختبر
  • نقاط XP والتتابع
  • تقدّم المحاضرة والملاحظات