σμ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›الوحدة 1 · حالات التسابق والمستكشف›الموضوع 1.1·~45 دمتوسط

حالات التسابق ولماذا توجد الأقفال

الموضع في المسارالوحدة 1 من 4 · الموضوع 1 من 2
1.1 حالات التسابق ولماذا توجد الأقفال
—درجة المختبر— / 100إجمالي XP—التتابع—

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

  1. 01سطر واحد وثلاث خطوات
  2. 02الجدول قائمة من أرقام الخيوط
  3. 03القفل يجعل الخطوات كتلة واحدة
  4. 04ما ستبنيه

يضيف خيطان 1 إلى العدّاد نفسه فينتهي العدّاد خاطئًا دون خطأ أو انهيار. السطر x += 1 ليس خطوة واحدة بل ثلاث، وترتيب تداخل الخيطين لهذه الخطوات هو ما يحدد الجواب. على 1085 يوم صعود لـSPY تضيع 542 تحديثًا ويعيدها القفل كلها.

01

سطر واحد وثلاث خطوات

لا يستطيع المعالج أن يجمع إلى قيمة موجودة في الذاكرة. إنه يحمّل القيمة في مسجّل ويضيف إلى المسجّل ثم يخزّن المسجّل في الذاكرة. إذن x += 1 هي load x ثم add 1 ثم store x. الخيط الواحد ينفذ الثلاث متتابعة ولا شيء يسوء. ومع خيطين قد تنفذ الآلة خطوة من أحدهما ثم خطوة من الآخر بأي ترتيب. لنرى ذلك نكتب آلة صغيرة. الخيط قائمة خطوات لكل خيط مسجّله الخاص، وتتشارك الخيوط قاموسًا من القيم المسمّاة. تنفّذ الدالة execute خطوة واحدة من خيط. نلعب أدناه الخطوات الست لخيطين يدويًا، خطوة لكل خيط بالتناوب.

main.pyPython
def execute(op, shared, regs, locks, tid):    kind = op[0]    if kind == "load":        regs[tid] = shared.get(op[1], 0)    elif kind == "add":        regs[tid] += op[1]    elif kind == "store":        shared[op[1]] = regs[tid]    elif kind == "lock":        if op[1] in locks:            return False        locks[op[1]] = tid    elif kind == "unlock":        del locks[op[1]]    return Trueshared, regs, locks = {"x": 0}, {0: 0, 1: 0}, {}turns = [(0, 0), (1, 0), (0, 1), (1, 1), (0, 2), (1, 2)]1for tid, step in turns:    execute(INCREMENT[step], shared, regs, locks, tid)    print("thread", tid, INCREMENT[step], "->", shared, regs)
thread 0 ('load', 'x') -> {'x': 0} {0: 0, 1: 0}
thread 1 ('load', 'x') -> {'x': 0} {0: 0, 1: 0}
thread 0 ('add', 1) -> {'x': 0} {0: 1, 1: 0}
thread 1 ('add', 1) -> {'x': 0} {0: 1, 1: 1}
thread 0 ('store', 'x') -> {'x': 1} {0: 1, 1: 1}
thread 1 ('store', 'x') -> {'x': 1} {0: 1, 1: 1}
  1. 1يحمّل الخيطان قبل أن يخزّن أي منهما: يحمل كل مسجّل 0.

اقرأ المخرجات سطرًا سطرًا. يحمّل الخيط 0 القيمة 0 ويحمّل الخيط 1 القيمة 0 أيضًا (لم يخزّن الخيط 0 بعد) ويضيف كلاهما 1 إلى مسجّل فيه 0 ويخزّن كلاهما 1. نفّذ كل خيط x += 1 ومع ذلك قيمة x هي 1 لا 2. ضاع تحديث. لا خطأ في شيفرة أي من الخيطين؛ الخطأ موجود في هذا الترتيب للخطوات فقط.

تعريفحالة التسابق

خطأ تتوقف نتيجته على ترتيب تداخل الخيوط لخطواتها. البرنامج نفسه صحيح في بعض الجداول وخاطئ في غيرها فيسهل أن يفوت الخطأ في الاختبار.

02

الجدول قائمة من أرقام الخيوط

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

main.pyPython
def run(programs, schedule, shared=None):    shared = dict(shared or {})    regs = {tid: 0 for tid in range(len(programs))}    pcs = [0] * len(programs)    locks = {}    for tid in schedule:        if pcs[tid] < len(programs[tid]) and execute(programs[tid][pcs[tid]], shared, regs, locks, tid):            pcs[tid] += 1    while any(pcs[t] < len(programs[t]) for t in range(len(programs))):        progress = False        for tid in range(len(programs)):            if pcs[tid] < len(programs[tid]) and execute(programs[tid][pcs[tid]], shared, regs, locks, tid):                pcs[tid] += 1                progress = True        if not progress:            raise RuntimeError("deadlock")    return shareddef serial_schedule(programs):    return [tid for tid, program in enumerate(programs) for _ in program]def round_robin_schedule(programs):    longest = max((len(program) for program in programs), default=0)    return [tid for step in range(longest) for tid, program in enumerate(programs) if step < len(program)]def lost_updates(programs, schedule, name="x"):    expected = sum(op[1] for program in programs for op in program if op[0] == "add")    return expected - run(programs, schedule).get(name, 0)two = [INCREMENT, INCREMENT]print(serial_schedule(two), run(two, serial_schedule(two)))print(round_robin_schedule(two), run(two, round_robin_schedule(two)))print("lost:", lost_updates(two, round_robin_schedule(two)))
[0, 0, 0, 1, 1, 1] {'x': 2}
[0, 1, 0, 1, 0, 1] {'x': 1}
lost: 1

يعطي الجدول التسلسلي [0, 0, 0, 1, 1, 1] القيمة 2؛ ويعطي جدول الدور الدائري [0, 1, 0, 1, 0, 1] القيمة 1 ويضيّع تحديثًا. لاحظ ما تفعله run حين ينتهي الجدول مبكرًا: تواصل الخيوط غير المنتهية أخذ الأدوار خطوة لكل خيط بترتيب الخيوط، فيكتمل كل برنامج دائمًا. ولاحظ أن الخيط المنتهي يتجاهل أدواره، فأي قائمة من أرقام الخيوط جدول صالح.

03

القفل يجعل الخطوات كتلة واحدة

يعالج القفل حالة التسابق بمنع التداخل. يأخذ الخيط القفل قبل load ويحرّره بعد store. والخيط الثاني الذي يطلب القفل في هذه الأثناء لا يحصل عليه: دوره لا يفعل شيئًا ويبقى عند الخطوة نفسها حتى يتحرر القفل. فتعمل الخطوات بين lock وunlock ككتلة واحدة ولا يستطيع خيط آخر التسلل بينها. تبني الدالة counter_program برنامج خيط يزيد عدّادًا بالقفل أو بدونه. وتجلب الأقفال خطرًا جديدًا يظهر في آخر الشيفرة: خيطان يأخذان قفلين بترتيبين متعاكسين قد يمسك كل منهما القفل الذي يحتاجه الآخر ولا يستطيع أحدهما التقدم أبدًا. هذا هو الجمود، وتكشفه run حين لا يحقق مرور كامل أي تقدم.

main.pyPython
def counter_program(increments, locked=False):    program = []    for _ in range(increments):        program += ([("lock", "m")] if locked else []) + INCREMENT + ([("unlock", "m")] if locked else [])    return programsafe = [counter_program(1, locked=True), counter_program(1, locked=True)]print(counter_program(1, locked=True))print(run(safe, round_robin_schedule(safe)))ab = [[("lock", "a"), ("lock", "b"), ("unlock", "b"), ("unlock", "a")],      [("lock", "b"), ("lock", "a"), ("unlock", "a"), ("unlock", "b")]]try:    run(ab, [0, 1])except RuntimeError as error:    print("error:", error)
[('lock', 'm'), ('load', 'x'), ('add', 1), ('store', 'x'), ('unlock', 'm')]
{'x': 2}
error: deadlock
القفل ليس مجانيًا

الخيط الذي ينتظر قفلًا لا يفعل شيئًا والخيط الذي يمسكه قد يوقفه نظام التشغيل مؤقتًا عند أي خطوة. وفي مسار حرج للتداول يعني ذلك اهتزازًا في الزمن لا يمكن تحديده. يبني بقية هذه المهارة بنى لا تحتاج أي قفل.

main.pyPython
def study(bars):    returns = [float(r) for r in bars["close"].pct_change().dropna()][:2000]    ups = [sum(1 for r in returns[start::2] if r > 0) for start in (0, 1)]    free = [counter_program(k) for k in ups]    locked = [counter_program(k, locked=True) for k in ups]    return {"up": sum(ups),            "serial": run(free, serial_schedule(free))["x"],            "racy": run(free, round_robin_schedule(free))["x"],            "locked": run(locked, round_robin_schedule(locked))["x"]}print(study(load_bars()))
{'up': 1085, 'serial': 1085, 'racy': 543, 'locked': 1085}
ما تقوله البيانات الحقيقية2000 عائد يومي لـSPY، خيطان يعدّان أيام الصعود، جدول دور دائري
أيام الصعود المطلوب عدّها1,085من 2000 يوم
الجدول التسلسلي1,085قيمة x في النهاية: عُدَّ كل شيء
الدور الدائري بلا قفل543قيمة x في النهاية: ضاع 542 تحديثًا
الدور الدائري بقفل1,085قيمة x في النهاية: لم يضع شيء
القيمة النهائية للعدّاد حسب الجدول
تسلسلي1085
دور دائري بلا قفل543
دور دائري بقفل1085

بلا قفل يُبقي جدول الدور الدائري الخيطين على بُعد خطوة واحدة فيحمّلان القيمة نفسها دائمًا ويُحتسب كل زوج من الزيادات مرة واحدة: يضيع نحو نصف أيام الصعود الـ1085. ويعيد القفل المجموع الدقيق بثمن إجبار خيط على الانتظار.

04

ما ستبنيه

يتبع المختبر الدرس: خطوة من خيط والآلة كاملة تحت جدول والجدولان البسيطان وعدّ التحديثات الضائعة وبرنامج خيط العدّ بقفل وبدونه ودراسة خيطين يعدّان أيام صعود SPY.

  • الدالة · السؤالتعتمد على
  • execute(op, shared, regs, locks, tid)ماذا تفعل خطوة واحدة وهل يجب أن ينتظر الخيط؟حساب بسيط
  • run(programs, schedule, shared)ماذا تتركه الخيوط تحت هذا الجدول؟execute
  • serial_schedule(programs)خيط بعد آخر؟حساب بسيط
  • round_robin_schedule(programs)خطوة لكل خيط بالتناوب؟حساب بسيط
  • lost_updates(programs, schedule)كم زيادة تختفي؟run
  • counter_program(increments, locked)ماذا يفعل خيط العدّ؟حساب بسيط
  • study(bars)كم تحديثًا تكلّف حالة التسابق على SPY؟counter_programrunlost_updates

أهم الخلاصات

  • `x += 1` ثلاث خطوات. تحميل ثم جمع ثم تخزين: إذا حمّل خيطان قبل أن يخزّن أي منهما ضاع تحديث دون أي خطأ ينبّهك.
  • الخطأ جدول. يمكن طباعة قائمة أرقام الخيوط وإعادة تشغيلها واختبارها؛ اختفى 542 تحديثًا من 1085 تحت جدول دور دائري واحد.
  • القفل يعالج التسابق ويضيف كلفة. يجعل الخطوات كتلة واحدة لكن الخيط المنتظر لا يفعل شيئًا وقفلان بترتيبين متعاكسين يسببان جمودًا.
تحقق سريع
ينفّذ خيطان كلٌّ منهما load x ثم add 1 ثم store x حين x = 0 وفق الجدول [0, 1, 1, 0, 0, 1]. كم تساوي x في النهاية؟
اختر إجابة.

الموضوع التالي: وجدت التحديث الضائع باختيار جدول يدويًا. لبرنامج حقيقي جداول أكثر من أن تُجرَّب يدويًا. في مستكشف التداخلات تعدّد كل جداول برنامج صغير وتعدّ التي تكسره وتحتفظ بالمستكشف أداةً لبقية المهارة.

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

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

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

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

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