σμ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—連続日数—

読了 6分コード例 4件

  1. 01チケットは配達ではない
  2. 02全スケジュールの検査
  3. 03連番が何をもたらすか
  4. 04これから作るもの

プロデューサ1つ・コンシューマ1つなら、位置の書き手は1つです。多数になると位置をアトミックに配る必要があり、チケットだけでは足りません。位置5を持つプロデューサはまだ要素を書いていないからです。各セルの連番が、そのセルが誰の番かを示します。プロデューサ2つの2,756通りとコンシューマ2つの1,600通りのスケジュールで、このキューは一度も壊れません。

01

チケットは配達ではない

capacity 個のセルを持つ有界キューは、増える一方の2つのカウンタを使います。プロデューサは enq への比較交換で次の位置を取り、コンシューマは deq への比較交換で次の位置を取ります。位置 p はセル p % capacity にあります。比較交換により予約は安全になり、2つのプロデューサが同じ位置を得ることはありません。しかし位置の予約は要素の書き込みではありません。交換と格納の間、セルは予約済みで空であり、いま読んだコンシューマはゴミを読みます。そこで各セルは、正しい瞬間にだけ進む連番を持ちます。

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}

3つの実行を読みましょう。[0, 0, 0] のあと、プロデューサは位置0を予約し(enq: 1)、data0 を格納し、seq0 を1に上げたので要素は準備完了です。コンシューマの3手番のあとは got0: 7、seq0: 2 で、セルは次の周回のために空きます。最後の実行では、コンシューマがプロデューサの予約と公開の間にのぞき、seq0 が1ではなく0なのを見て、手ぶらで去ります。ゴミは読みません。「まだ準備できていない」と言うだけで、要素は後の試行で見つかります。

02

全スケジュールの検査

キューは今度は何を約束すべきでしょうか。プロデューサは各要素を位置ごとに記録し(put3)、コンシューマは取った要素を予約した位置ごとに記録します(got3)。キューが正しければ、取り出された各位置にはそこに入れた要素があり(mismatch なし)、取り出された位置は 0, 1, 2, ... と穴も重複もなく並びます(gap なし)。これがキューの順序、つまり位置の順序です。violations は最終状態からこの2つの事実を読み、verify は任意の数のプロデューサとコンシューマで探索器を動かします。ここでは、2要素の1プロデューサと2コンシューマ、そして1要素と1コンシューマです。

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 を見ずにすぐセルを読みます。1要素のプロデューサに対して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
容量は2以上でなければならない

セルが1つだと、「位置pの準備完了」を意味する値 p + 1 が「位置 p + 1 のために空き」を意味する値と同じになり、プロデューサが未読の要素を上書きしかねません。そのため実際の実装は容量を2のべき乗(最低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の最初の2つの終値(9,369と9,305セント)を2セルのキューに通す
プロデューサ2つ・コンシューマ1つ2,756通りのスケジュール、不変条件違反0
プロデューサ1つ・コンシューマ2つ1,600通りのスケジュール、不変条件違反0
不注意なコンシューマ1020通り中が存在しない要素を読む

キューの両方の形は、探索器を証人として、すべてのスケジュールで正しい一方、連番の検査を外した版は(ずっと小さい)空間の半分で失敗します。プロデューサとコンシューマは互いを待ちません。準備ができたものが見つからないコンシューマは単に戻り、満杯のキューを見つけたプロデューサは False を返します。これがロックフリーキューの代償であり贈り物です。操作は失敗しうるが、決してブロックしません。

04

これから作るもの

ラボは講義の順に進みます。連番付きの空のキュー、比較交換による予約と公開を行うenqueueとdequeue、2つの不変条件の検査、任意の数のプロデューサとコンシューマで探索器を動かす検証関数、そしてSPYの終値2つでの研究です。

  • 関数 · 問い前提
  • init_queue(capacity)空のキューはどんな形か?単純な算術
  • enqueue(shared, item, capacity)プロデューサはどう予約し公開するか?単純な算術
  • dequeue(shared, capacity)コンシューマはどう予約し解放するか?単純な算術
  • violations(final)最終状態はどの不変条件を壊すか?単純な算術
  • verify(producers, consumers, capacity)キューはすべてのスケジュールで正しいか?explore_allviolations
  • study(bars)両方の形はSPYの終値で成り立つか?verify

重要ポイント

  • チケットは比較交換、準備完了は連番で。 位置を予約するとその位置は自分のものになり、連番がその中の要素の準備完了をみなに知らせます。
  • 準備できていないのはエラーではない。 準備できたものがないコンシューマは None を返して後で再試行します。待たず、ゴミも読みません。
  • 連番を外した対照とともに証明する。 1,600通りと2,756通りで違反なし。不注意なコンシューマは20通り中10通りで壊れます。
クイックチェック
キューのセルは4つです。コンシューマが位置6を予約してそのセルを解放しました。そのセルの連番はいくつで、どの位置のために空いていますか。
答えを選んでください。

次のトピック:ここまでは、全スケジュールを実行して小さなプログラムの正しさを証明してきました。並行コードのテストでは、それを実際のチームが使う道具と比べます。1回の実行を観察して競合しうるアクセスを指摘するレース検出器と、あなたの探索器と同じことを、規模を広げる工夫付きで行うモデル検査器です。

ラボ:採点される内容

6タスク · 100点
  • 空のキュー5点
  • enqueue25点
  • dequeue25点
  • 不変条件10点
  • 全スケジュールで検証する15点
  • SPYの表での研究20点
エクスプローラー · 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と連続日数
  • 講義の進捗とメモ