σμ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 スキル›モジュール 2 · アトミック操作・順序・保証›トピック 2.3·~45 分上級

進行保証:obstruction-freeからwait-freeまで

スキル上の位置モジュール 2/4 · トピック 3/3
2.3 進行保証:obstruction-freeからwait-freeまで
—ラボスコア— / 100合計XP—連続日数—

読了 6分コード例 4件

  1. 01止められた1つのスレッド
  2. 02ロックフリー:誰かは必ず終わる
  3. 03ウェイトフリー:全員が終わる
  4. 04これから作るもの

3つのカウンタは、どのスケジュールでも正しい答えを出しますが、同じではありません。最悪のタイミングで1つのスレッドを止めると、スピンロックは全員を止め、比較交換のカウンタは誰かを通し、fetch-and-add のカウンタはどのスレッドも1手番で終えさせます。これが進行保証の梯子の3段です。

01

止められた1つのスレッド

実際のOSは、いつでもスレッドをコアから外せます。タイムスライスが終わる、ページフォルトでディスクへ送られる、割り込みが来る、などです。良い並行構造はこれに耐えるべきです。この問いを敵対者で正確にします。敵対者は好きな prefix の手番を実行し、その後1つのスレッドを永久に止め、他の全員に budget ラウンドを与えます。止められていないスレッドで終わっていないものが残るなら、その構造は止められます。スピンロックはこのテストに最も単純な形で落ちます。敵対者がロックを持つスレッドを止めると、他のスレッドは予算が尽きるまで空回りします。

main.pyPython
def locked_increments(k):    def thread(shared):        for _ in range(k):            while True:                yield                if not shared.get("lock", 0):                    shared["lock"] = 1                    break            yield            value = shared.get("x", 0)            yield            shared["x"] = value + 1            yield            shared["lock"] = 0    return threaddef progress_without(make_threads, prefix, paused, budget=50):    n = len(make_threads({}))    allowed = [tid for tid in range(n) if tid not in paused]    _, alive = run_prefix(make_threads, list(prefix) + allowed * budget)    return [tid for tid in allowed if tid not in alive]lk = lambda shared: [locked_increments(1)(shared), locked_increments(1)(shared)]print(run_prefix(lk, [0]))print("thread 0 paused holding the lock:", progress_without(lk, [0], [0]))print("thread 0 finished first:         ", progress_without(lk, [0, 0, 0, 0], [0]))
({'lock': 1}, [0, 1])
thread 0 paused holding the lock: []
thread 0 finished first:          [1]

1手番のあと、スレッド0がロックを持ちます('lock': 1)。そこで止めると、スレッド1は50ラウンドたっても何も終えません([])。先にスレッド0に4手番を実行させるとロックは再び空き、スレッド1は終わります([1])。違いは敵対者がどこを突くかだけです。ロックは、クリティカルセクション内の一時停止を、プログラム全体の一時停止に変えてしまいます。

02

ロックフリー:誰かは必ず終わる

is_blocking は敵対者を自動化します。数手番の接頭辞をすべて試し、生きているスレッドを順に止めて、他のスレッドがまだ終わるかを progress_without に尋ねます。スピンロックでは、1手番のあとに答えが「はい」になります。比較交換のカウンタでは決してそうなりません。読み出しと交換の間で止められたスレッドは値を変えないままなので、他のスレッドの交換は成功します。これがロックフリーの定義です。1つのスレッドが永久に止まっても、システム全体は必ず前進します。

main.pyPython
def is_blocking(make_threads, depth=3, budget=50):    def visit(prefix):        _, alive = run_prefix(make_threads, prefix)        for paused in alive:            finished = progress_without(make_threads, prefix, [paused], budget)            if len(finished) < len([tid for tid in range(len(make_threads({}))) if tid != paused]):                return True        if len(prefix) < depth:            return any(visit(prefix + [tid]) for tid in alive)        return False    return visit([])cs = lambda shared: [cas_increments(1)(shared), cas_increments(1)(shared)]at = lambda shared: [atomic_increments(1)(shared), atomic_increments(1)(shared)]print("spin lock:", is_blocking(lk))print("compare-and-swap:", is_blocking(cs))print("fetch-and-add:", is_blocking(at))
spin lock: True
compare-and-swap: False
fetch-and-add: False

比較交換のカウンタも fetch-and-add のカウンタも、ブロックしません。梯子に3段目があるのは、ロックフリーでも1つのスレッドにとっては悪い日があり得るからです。すべての競争に負けることがあります。

03

ウェイトフリー:全員が終わる

ウェイトフリーな操作には、自分の手番数に対する上限があり、どの隣人もそれを破れません。worst_own_turns はそれを測ります。すべての完成したスケジュールでスレッドが取る手番数の最大値です。そして classify が決定的な問いを立てます。隣のスレッドの増分が1回のときと3回のときで、スレッド0の最悪ケースは同じか。同じならスレッド0はウェイトフリーです。増えるなら、隣人がスレッド0により多くリトライさせられるので、構造はロックフリーにとどまります。

main.pyPython
def worst_own_turns(make_threads, tid):    worst = 0    def visit(prefix):        nonlocal worst        _, alive = run_prefix(make_threads, prefix)        if not alive:            worst = max(worst, prefix.count(tid))            return        for other in alive:            visit(prefix + [other])    visit([])    return worstdef classify(factory, others=(1, 3)):    def program(m):        return lambda shared: [factory(1)(shared), factory(m)(shared)]    if is_blocking(program(others[0])):        return "blocking"    light, busy = worst_own_turns(program(others[0]), 0), worst_own_turns(program(others[1]), 0)    return "wait-free" if light == busy else "lock-free"for neighbour in (1, 2, 3):    cas = lambda shared: [cas_increments(1)(shared), cas_increments(neighbour)(shared)]    atomic = lambda shared: [atomic_increments(1)(shared), atomic_increments(neighbour)(shared)]    print(neighbour, "increments next door: CAS", worst_own_turns(cas, 0), "turns, fetch-and-add", worst_own_turns(atomic, 0))print([classify(f) for f in (locked_increments, cas_increments, atomic_increments)])
1 increments next door: CAS 4 turns, fetch-and-add 1
2 increments next door: CAS 6 turns, fetch-and-add 1
3 increments next door: CAS 8 turns, fetch-and-add 1
['blocking', 'lock-free', 'wait-free']
どれを選ぶか

ウェイトフリーは最も強く、設計は最も高価ですが、fetch-and-add のように手が届く場合もあります。ロックフリーはキューやスタックの現実的な目標です。ロックは、保持者が止まっても困らない場所なら構いませんが、止まってはならない経路には決して使えません。

main.pyPython
returns = [float(r) for r in load_bars()["close"].pct_change().dropna()][:6]ups = [sum(1 for r in returns[start::2] if r > 0) for start in (0, 1)]for name, factory in (("locked", locked_increments), ("cas", cas_increments), ("atomic", atomic_increments)):    program = lambda shared, f=factory: [f(k)(shared) for k in ups]    blocking = is_blocking(program)    print(name, classify(factory), blocking, None if blocking else worst_own_turns(program, 0))
locked blocking True None
cas lock-free False 8
atomic wait-free False 1
実データが示すものSPYの最初の6件のリターンの上昇日:スレッド0は増分1回、スレッド1は3回
スピンロックblocking1つのスレッドを止めると他方も止まる
比較交換lock-freeスレッド0の最悪ケース:8手番
fetch-and-addwait-freeスレッド0の最悪ケース:1手番
隣の仕事量別のスレッド0の最悪の自手番数
fetch-and-add・隣が3回1
CAS・隣が1回4
CAS・隣が2回6
CAS・隣が3回8

棒グラフを読みましょう。fetch-and-add のスレッドは隣がどれだけ忙しくても1手番ですが、比較交換のスレッドは隣の増分が増えるにつれて4、6、8手番になります。隣の増分が1回増えるごとに、スレッド0はもう1回競争に負けて読み出しと交換をやり直すことがあります。この増え方こそが、ロックフリーとウェイトフリーを分けるものです。

定義進行保証

ブロッキング:1つのスレッドを止めると他も止まりうる。ロックフリー:必ずどれかのスレッドが終わる。ウェイトフリー:どのスレッドも自分の有限の手番数で終わる。この並びで、各段は一つ前の段を含意します。

04

これから作るもの

ラボは講義の順に進みます。スピンロックのカウンタ、一部のスレッドを止めた実行、1つのスレッドを止めると他が止まるかのテスト、スレッド自身の手番数の最悪ケース、カウンタの分類、そしてSPYの上昇日に対する3つのカウンタの研究です。

  • 関数 · 問い前提
  • locked_increments(k)ロックを待つカウンタは?単純な算術
  • progress_without(make_threads, prefix, paused, budget)一部が止まったとき誰が終わるか?run_prefix
  • is_blocking(make_threads, depth, budget)1つのスレッドを止めると全員が止まるか?progress_without
  • worst_own_turns(make_threads, tid)スレッドは最大何手番必要か?run_prefix
  • classify(factory, others)ブロッキング、ロックフリー、ウェイトフリーのどれか?is_blockingworst_own_turns
  • study(bars)SPYで3つのカウンタはどこに位置するか?classifyis_blockingworst_own_turns

重要ポイント

  • ロックは全員を止めうる。 保持者を1手番後に止めると、他方のスレッドは50ラウンドたっても何も終えません。
  • ロックフリーとは、誰かが必ず終わること。 ただしスレッドが全部の競争に負けることはあります。隣が働くほど、比較交換は4、6、8手番かかります。
  • ウェイトフリーには隣人が破れない上限がある。 fetch-and-add は隣が何をしても1手番で終わります。
クイックチェック
あるスレッドが比較交換で1回増やし、別のスレッドが5回増やします。保証されるのはどれですか。
答えを選んでください。

次のトピック:キューが何を約束できるかが分かりました。単一プロデューサ・単一コンシューマのリングバッファでは、書き手1つと読み手1つ、固定メモリ、アトミックなRead-Modify-Writeなしという最も単純で実用的なロックフリーキューを作り、ティックを失ったり並べ替えたりしないことを探索器で確かめます。

ラボ:採点される内容

6タスク · 100点
  • スピンロックのカウンタ10点
  • スレッドを止めて実行する20点
  • 1つの停止で全員が止まるか20点
  • スレッドの最悪ケース15点
  • 保証に名前を付ける10点
  • SPYの表での研究25点
エクスプローラー · lab-lf-05
▾ lab-lf-05
▾ data
…
bash · ブラウザ内
learner@quantcraft:~/lab-lf-05$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

このトピックをリセットしますか?

lab-lf-05 の内容はすべてスターターファイルに戻り、ターミナルも消去されます。現在の解答は失われ、元に戻せません。

リセットされるもの
  • main.py → スターターコード
  • ターミナル履歴とテスト出力
  • 未提出の変更
保持されるもの
  • ラボの最高スコア
  • XPと連続日数
  • 講義の進捗とメモ