Surelock:Rust 用のデッドロックフリーミューチュエックス

2026/04/09 2:36

Surelock:Rust 用のデッドロックフリーミューチュエックス

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

surelock ライブラリは、ロック安全性をコンパイル時に証明することで Rust プログラムがデッドロック不可能であることを保証し、危険な実行時チェックやパニックの必要を取り除きます。このアプローチは、従来の手法が沈黙して失敗することが多い並行プログラミングにおける主要な信頼性課題に対処します。システムは、2 つの特別なメカニズムによってカーフマン(Coffman)の循環待機条件を破ることでこれを達成します:LockSet は、同じレベル内での原子化ロックを管理し、一意で単調増加する ID を使用して決定論的な順序を確保し、Level は、異なるロックレベル間の厳格な全順序を強制してサイクルを防ぎます。設計は、happylock プロジェクトからのような概念(例:原子能力トークン)を取り入れていますが、より安定したアドレスを得るためにこれらの ID を使用することでそれらを改善しています。lock_tree などのツールで使われるより複雑な依存関係グラフとは異なり、surelock は、コンパイラが簡単にチェックできるこの厳格な順序付けシステムを用いて検証を簡素化します。柔軟性を必要とする開発者向けに、

Mutex::unchecked_lock()
を通じて手動での
unsafe
使用のためのオプション「エスケープハッチ」が存在しますが、これは機能フラグによって制限されています。結局のところ、ユーザーは最小の実行時依存関係(
thiserror
を使用)を持つ堅牢な並行性プリミティブを入手し、標準系システムから原子命令を持たないマイコンまで、多様な環境をサポートしつつ(
portable-atomic
および
critical-section
を使用)、双務的な MIT/Apache-2.0 ライセンスの下で
unsafe
コードを内部実装に厳格に制限しています。

本文

こんにちは、r/rust の皆様!

この作業への関心と、誠に楽しい対話に心から感謝申し上げます ❤️

私はデッドロック(互斥排他によるシステム停止)を嫌います。おそらく皆様も同様でしょう。Fission に在籍していた頃は、誰かが Mutex の導入を提案するたびに、「Mutex は NG!」というチャントが始まりました。「Mutex!DEADLOCK!Mutex!DEADLOCK!」という掛け声です。デッドロックは潜伏しており、コードレビューでは完全に目に見えないし、CI を何千回もパスさせられた挙句、誰も予期しないリクエストパターンが 3 時の深夜にシステムをロックアウトさせてしまうこともあります。

デッドロックには独自のトレードオフがありますが、Haskell の TVars は懐かしいです。純粋性(purity)を保証できない言語では、本物の TVar を実装する方法がないと考えられています。私たちは「锁フリープログラミング」を好むべきですが、Rust の標準的なスタイルでも Mutex が非常に一般的です。「アクターモデルはデッドロックを排除する」とよく聞きますが、Elixir でそれなりのコードを書いた者として告げるのは、これは 100%の嘘です(ただし発生頻度は少ないことは事実)。

Rust は有名なようにコンパイル時に関データ races(データ競合)を検出しますが、デッドロックについてはどうでしょうか?Mutex を受け取り、背中をポンして「がんばってね、ベイベー」と言われるだけです。コード分析に役立ついくつかのツールは存在しますが、開発中のフィードバックを求めています。私はこの問題へのより良いアプローチについてしばらく考えており、他のいくつかの試みも検討した結果、Rust の多くの一般的な使用ケースをカバーすると考えられる、使いやすさと機能性のバランスが良いと思われる「surelock」というデッドロックフリーなライブラリを実現しました。コードがコンパイルさえすれば、デッドロックしません。Result も Option もなく、ロック経路でのランタイムパニックもありません。すべての取得は、コンパイラーによって安全性が証明されているか、ビルド時に拒絶されます¹。

これはプレリリース版です。コアな設計は堅固だと考えますが、荒削りの部分があることに驚かないのはむしろ自然でしょう。フィードバックとコントリビューションを大歓迎します!

TL;DR

  • デッドロックは、Coffman の 4 つの条件が同時に満たされた時に発生する
  • Surelock は「循環待機(circular wait)」という条件を、2 つの補完的なメカニズムによって破る
  • 同じレベルのロックは、原子かつ決定論的な順序で取得される (LockSet)
  • 異なるレベルのロックは、累積的に取得され、コンパイル時の順序強制が施される (Level)
  • 全体の公開 API は安全 — unsafe はローカルの Mutex 内部に限定されている
  • no_std 互換であり、ランタイム依存関係は zero(ゼロ)²

なぜ注意するだけではダメなのか?

正直な答えは、「注意すればいい」という考え方はスケールしないからです。コードを組み立てる過程で、足を引っ掛けるのは簡単です。「待ってください、それが Rust が私たちに助けてくれるべきことではないですか?」 borrow チェッカーはコンパイル時にデータ競合を捉えるので、デッドロックについても同じことが期待できないでしょうか?

根本的な問題はよく理解されています。Coffman らは 1971 年にデッドロックが発生するための 4 つの必要条件を特定しました:

条件意味
相互排他 (Mutual exclusion)少なくとも一つの資源が排他的に保持されている
保持かつ待機 (Hold and wait)スレッドは一つのロックを持ちながら、別のロックを待っている
先行不能性 (No preemption)ロックは強制的に取り上げられない
循環待機 (Circular wait)待機グラフにサイクルが存在する

これらの条件の一つでも破れば、デッドロック是不可能になります。相互排他こそが Mutex の存在意義であり、先行不能性は新たな種類のバグを引き起こします。残ったのは「保持かつ待機」と「循環待機」です。

私たちは 50 年以上も前に(!)解決策の空間を特定しました。非常に棘のある問題で、合意された単一の解決策がないのに、Mutex は十分に有用なので依然として使用されています。これは、何か Mutex を完全に置き換えるものが出るまで、Mutex を安全に使うためのユーザビリティ(使いやすさ)向上が領域だということです。

安全性技術が正確に安全な使用法と一致するのは稀です。型システムは、いくつかの不整合を許容するか、正当な用途を排除するか(あるいは両方)してしまうことで有名です — そのために、不整合を排しつつすべての安全な使用法を直接表現できるより洗練された型システムを作るための多大な努力が続けられています。鍵は、扱うのに最も容易で論理的なモデルを導入しながら、それらを可能な限り一致させることにあります。私は、ツールたちが私たち自身に問題について考える手助けをするようにしたいと考えます。

Surelock は物理世界の比喩を基盤としています:ロックと相互作用するには鍵が必要です。当方でいうと、Mutex が使用中である間はその鍵を保持し、解凍するまで鍵を返すだけです。 これを

MutexKey
と呼び、線形(linear)³ スコープトークンと呼んでいます。ロックスコープに入る時に一つ獲得します。
.lock()
を呼ぶと、その鍵が消費され、ガードと一緒に新しい鍵が返されます。新しい鍵には既にお持ちのロックを何らかに示す型レベルのレコードが付いており、コンパイラーは今後もどのロックを取得できるかを知ることができます。後ろ向きに行おうとしてもコードはコンパイルしません。

💡 これが核心となるトリックです:鍵を移動しかできない値として作り、すべての取得を通じて流すことで、現在のロック状態のコンパイル時検証(witness)を得られます。グローバルな分析もランタイムでの追跡も不要 — 型チェッカーが最も得意とすることをします。

この比喩はこれだけで尽きませんが:

MutexKey
は実際に複数の Mutex を同時に原子操作でロックする能力を与えます。Surelock のロックはレベルにグループ化され、累積的な取得を可能にし、ロック返却にはより制限された鍵(attenuated key)が返されます。

先行事例(Prior Art)

設計に影響を与えた既存のライブラリが 2 つあり、それらに適切なクレジットを付けたいと思います。

happylock

  • botahamec が、能力トークンとソートされた複数ロック取得を組み合わせてデッドロックを防ぐという核心的な洞察を導入しました。Surelock はこのパターンを直接借用しています。
  • 両者のアプローチが異なる点:happylock は「保持かつ待機」の条件を破ります。コレクションを通じてロックすると、鍵が消費され、解放されるまで全く新たなロックを取得できません。これは安全ですが、「設定を読み、どのアカウントを更新するか読む、次にそのアカウントをロックする」といったことは不可能です。累積的な取得が必要な並行システムにおいて、これは実質的な制限です — 累積的なロックが必要な場合は、真に累積的なロックが必要です。
  • happylock はまた、メモリアドレスによってロックをソートしますが、Vec の再割り当てや移動では安定しません。ライブラリはアドレスの安定性を維持するために unsafe(Box::leak, transmute など)を用い、その不安全性が公開 API に波及します。

lock_tree

  • Google の Fuchsia プロジェクトから、
    LockAfter
    特異体によるコンパイル時ロック順序を導入しました。レベル A がレベル B より来ると宣言し、逆順で取得しようとするコードはコンパイラーが拒絶します。
  • Surelock はこれをいくつかの点で拡張しています:同じレベルでのマルチロック(lock_tree は表現できない)、
    new_higher
    を通じたインスタンスごとのレベル割り当て、スコープ唯一性強制など。Surelock は意図的に lock_tree の DAG ベースの順序付けを厳密な全順序に替えており、その理由は後述します。

二重メカニズム設計

Surelock は 2 つのメカニズムを使用し、補完的なケースをカバーします。両者は重複しません:

メカニズム何时取得強制
LockSet同じレベルでの複数のロック原子操作(全か無か)ランタイムで安定 ID でソート
Levels (Level)異なるレベルのロック累積的コンパイル時特異体境界

同じレベル:LockSet

各 Mutex は作成時にグローバルな

AtomicU64
カウンターから一意且つ単調増加する
LockId
を受け取ります。ID は Mutex の内部にあり、 Mutex と一緒に移動します — アドレスの安定性は不要です。 同じレベルで複数のロックが必要になるとき、LockSet は構造時に ID で事前にソートします。2 つのスレッドが逆順で同じロックを要求しても、両方とも同じ取得順序にソートされます。サイクルは形成されません。

let alice = Mutex::new("alice");
let bob = Mutex::new("bob");

// アーグメントの順序に関係なく、取得順序は決定論的
let set = LockSet::new((&alice, &bob));

key_handle.scope(|key| {
    let (a, b) = set.lock(key);
    // 両方ロック済み、デッドロック不可能
});

スレッド A スレッド B │ │ ▼ ▼ LockSet::new((&acct_1, &acct_2)) LockSet::new((&acct_2, &acct_1)) │ │ ▼ ▼ sorted: [acct_1, acct_2] sorted: [acct_1, acct_2] │ │ ├─ acct_1 のロックを取得 │ │ ├─ acct_1 のロックを待つ ├─ acct_2 のロックを取得 │ │ │

  │                               │
  ├─ acct_2 を解放                │
  │                               │ (まだ lock 1 を待っている)
  ├─ acct_1 を解放                │
  │                               ├─ acct_1 のロックを取得
  │                               ├─ acct_2 のロックを取得
  │                               │
  ▼                               ▼
  OK                              OK (サイクル不可能)

#### 異なるレベル:コンパイル時順序付け
ロックが異なる種類の資源を表す場合(例えば、設定ガード対アカウント別ロック)は、異なるレベルに割り当てます。`Level<N>` タイプと `LockAfter` 特異体境界は、厳密な昇順の取得を強制します:

```rust
use surelock::level::{Level, lock_scope};
type Config = Level<1>;
type Account = Level<2>;

let config: Mutex<_, Config> = Mutex::new(AppConfig::default());
let account: Mutex<_, Account> = Mutex::new(Balance(0));

lock_scope(|key| {
   let (cfg, key) = config.lock(key);  // レベル 1 -> OK
   let (acct, key) = account.lock(key); // レベル 2 で 1 の後 -> OK
   // account.lock(key) 次に config.lock(key) -> コンパイルエラー
});
```

鍵(ハッ!)は `MutexKey` です。各 `lock()` 呼び出しで消費され、新しいレベルで再発射されます。後ろ向きに行おうとして(例えば `Level<3>` に続いて `Level<1>` を取得しようとした場合)、`LockAfter` 境界が満たされないため、コンパイラーは親切な(カスタム)エラーメッセージを出力して拒絶します。

#### なぜ全順序ではなく DAG か?
これは意図的な設計決定です。`lock_tree` は DAG を使用し、ブランチ A と B が相互依存せず、互いに優先されなくてもよいことを宣言できます。素晴らしいようですが、微妙な問題があります:スレッド 1 が A 然後 B を取得し、スレッド 2 が B 然後 A を取得し、両方の順序付けが DAG で有効なら、コンパイラーが快く承認したデッドロックが発生します。
ロックは `(Level, LockId)` によって全順序でソートされます。全順序はより制限的ですが、実際には健全です。システムで参加する 2 つのロックがあれば、それらの相対的な順序は固定されます。概念的に DAG を線形化することもできます(多くの操作ベース CRDT のように)が、それは論理的になりにくく、追加の機構を多数加えることになります。

### キーの取得

両方のメカニズム(LockSet とレベル)は `MutexKey` を使用します。鍵をスコープの一種と考えられます:各 `lock()` 呼び出しで消費され、新しいレベルで再発射されます。同一不変ライファイトタイム(同じ技法として `std::thread::scope` のように)でブランド付けされており、クロージャーから逃げることはできず、`!Send` なので作成スレッドにとどまります。
取得方法は暗黙的と明示的な 2 つあります。

#### 暗黙的:lock_scope
多くの人がこれを使うはずです。std 上では `lock_scope` は内部にすべてのセットアップを処理し、単に書けば:

```rust
use surelock::{Mutex, lock_scope};

let data = Mutex::new(42);

lock_scope(|key| {
   let (guard, key) = data.lock(key);
   assert_eq!(*guard, 42);
});
```

内部では、`lock_scope` は `thread_local! KeyHandle` を管理します。`KeyHandle` の `scope` メソッドは `&mut self` を取るので、borrow チェッカーがネストされたスコープを防止し、アクティブなスコープの中で新しいロックスコープを開始することはできません。

#### 明示的:Locksmith と KeyVoucher
no_std、組み込み、あるいはどのコアがどの能力を受け取るかより厳密に制御したい場合のために、完全に明示的な連鎖があります:

- **Locksmith**: プロジェクト全体でのシングルトン(`!Send`)。`KeyVouchers` を発行し、これらは `Send` なので、セットアップ中にキーをスレッド間で分散できます。voucher は宛先スレッドで交換され、`KeyHandle` が得られ、これは `!Send` で元の場所にとどまります。
- これは多くの式(ceremony)のように聞こえますが、主に安全不変を強制するための型機構です。bare metal で `thread_local!` がない場合、このレベルの制御は望ましく、初期化時にどのコアが鍵を受け取るかを正確に決めることができます、型システムにより他の誰も空から鍵を作り出すことはできません。

### 図表

以下に図解例を示します。ロックレベルは厳密なコンパイル時シーケンスにあることがわかりますが、ランタイムで割り当てられるロック ID は任意の順序で表示されます。これは完全に問題ありません;それらは単にすべての呼び出し元之间で一致している必要があります。
ここでは、レベル 0 より「上」をロックできるルートキーから始めます。2 つのレベルにわたってロックを取得し、ID でソートしてその順序で取得します(以前的时间軸で説明した理由により)。これはルートキーを消費し、レベル 2 より上のロックができるキーを放出します。将来的には、レベル 3 と以降をロックでき、解放するまで以前の鍵を回収できます。

```
                                               Key{Level > 0}
                                                      │
┌─────────Level 1───────────┐                          ▼
│                           │             ┌─────────LockSet────────┐
│                           │             │                        │
│   lock_1                  │             │                        │
│                lock_3 ────┼─────────────┼────▶ lvl_1-lock_3 ─────┼────────▶ guard-3
│                           │             │                        │
│        lock_8 ────────────┼─────────────┼────▶ lvl_1-lock_8 ─────┼────────▶ guard-8
│                           │             │                        │
└───────────────────────────┘             │                        │
                                         │                        │
┌─────────Level 2───────────┐             │                        │
│                           │             │                        │
│                           │             │                        │
│                lock_5 ────┼─────────────┼────▶ lvl_2-lock_5 ─────┼────────▶ guard-5
│                           │             │                        │
│    lock_7                 │             │                        │
│                           │             │                        │
└───────────────────────────┘             └────────────────────────┘
```

### no_std と組み込み

このクレートは核心で `#![no_std]` であり、alloc のみを必要とします。std 上では、デフォルトバックエンドとして `StdMutex` を得られ、`lock_scope` は便利なエントリポイントとなります。no_std 上では、独自のバックエンドを用意する必要があります — `lock_api::RawMutex` を実装する任意のものは機能フラグの後ろにブランクン impl 経由で動作します。
CAS を持つネイティブがないターゲット(例:Cortex-M0)も、`portable-atomic` および `critical-section` 機能をサポートしています。これは重要だと考えます;デッドロックフリー你最必要としている場所 — デバッグアタッチされた bare-metal ファームウェア — は、まさに現在のツールが最もあなたが助けられない場所だからです。

### エスケープハッチ(Escape Hatch)
現実のコードは時には規則を破る必要があることがあります。Surelock は `Mutex::unchecked_lock()` をエスケープハッチ機能フラグの後ろで提供します。これらは機能ゲート付きなので、使用すれば Cargo.toml で表示され、grep でき、コードレビューで目立います。IMO これは代替案よりも優れています;開発者が静かに `parking_lot::Mutex` を Surelock の Mutex に沿って使うことでシステム全体を無効にすることです。

### まとめ
デッドロックは理論的に解決された問題であり、1971 年以来どのように防ぐかを知っています。挑戦は、その防止が実際の使用で十分使いやすくすることです。Surelock はそれにわたる私の試み:Rust の型システムを活用して正しいことは容易なものにし、誤ったことをコンパイラーエラーにします。
このクレートは Codeberg で公開され、crates.io にも公開されています。MIT/Apache-2.0 で双重ライセンスです。フィードバックを大歓迎し、特に組み込みまたは no_std ターゲットで作業されている方々から — そこではまだ実世界テストが少ないからです。

¹ 例外が一つ:同じ Mutex を原子操作で一度以上に取得すること。このエッジケースに満足していませんが、自分自身と共に同じ Mutex を取ることはあまり一般的ではありません。
² `thiserror` のみが唯一のランタイム依存関係であり、ランタイム上ゼロコストです。デフォルト std バックエンドは直接 `std::sync::Mutex` をラップします。
³ 技術的には Rust の型システムではアフィン(dropped できるが複製できない)ですが、意図は線形です — 使用するのが期待され、API は設計されているのでそれを早期にドロップしてもスコープが終了するだけです。

同じ日のほかのニュース

一覧に戻る →

2026/04/12 1:47

小規模モデルも、Mythos が特定した脆弱性を見出すことができた。

## Japanese Translation: 最新の研究における最も重要な示唆は、高度なサイバーセキュリティ防御がもはや巨大で独佔的な AI モデルにのみ依存しなくなったという点です。代わりに、現実世界のセキュリティアプリケーションにおいて、生のモデル知能よりも優れたシステムオーケストレーションおよび検証レイヤーの方がはるかに重要であることが判明しました。この結論は、Anthropic の Mythos をさまざまな「フロンティア」モデルと比較した AISLE コンソーシアムによる独立したテストによってさらに裏付けられました。驚くべきことに、約 36 億〜51 億個のアクティブパラメータを持つ小型のオープンウェイトモデルが、複雑なエクスプロイトチェーンを成功裡に回復し、特定のエクスプロイト不可能なコードの同定や基礎的なセキュリティ推論といった特定のタスクにおいてより大きなモデルを上回るパフォーマンスを示しました。例えば、51 億パラメータを持つモデルは、27 年前の OpenBSD のバグに関する完全なチェーンを完全に同定した一方、一部の大型モデルでは単純な論理トレースに失敗したり、修正済みコードで誤検出を生じたりしました。これらの発見は、業界が生のモデル能力を追求することから転じて、検証を扱えかつ信頼を維持できる安価なオープンモデルを広く展開する信頼できるシステムを構築する方向へ移行できるようであることを示しています。これにより、企業はエリート知能への独占的なアクセスを必要とせずとも、コスト効率の高いオープンモデルを普及させることが可能になります。

2026/04/12 5:58

アップルシリコンと仮想マシン:VM の台数制限(2)を超える方法(2023 年)

## Japanese Translation: 主要なポイントは、Apple Silicon Mac が macOS Ventura ソフトウェア契約条項に組み込まれた閉源的な XNU カーネルによって実施される正式な 2 VM の制限を回避できることです。これは、カスタム開発用カーネルをインストールすることで可能となり、特定のブート引数(`hypervisor=` および `hv_apple_isa_vm_quota`)を使用することにより、最大 255 つの同時仮想マシンを動作させることが可能です。成功するには、Mac をシャットダウンし、リカバリモードで起動し、システム完全性保護(SIP)を無効にし、`kmutil` を使用してカスタムカーネルコレクションを作成および設定する必要があります。検証は `sysctl kern.osbuildconfig` を使用し、NVRAM のブート引数をチェックすることで実施できます。 しかし、このカスタマイズには重大なトレードオフが伴います。ユーザーは簡素化された OS アップデートを失い、後で変更を取り戻すために `bputil` によるポリシーリセットの手動管理が必要となります。著者は M2 Pro MacBook Pro で macOS VM を 9 つ実行し、副作用として熱発生とファンの作動が確認したことでこの方法を成功裏にテストしました。大規模な開発環境では技術的に実装可能ではありますが、プロセスには高度な専門知識が必要です。特に recoveryOS テルミナルを通じてブートポリシーを扱う技能が必要で、コアセキュリティプロトコルを損なうことなくシステム安定性を維持する必要があります。

2026/04/12 5:08

保存エネルギーゼロにおいて 447 TB/cm² を達成し、フルオログラファイン上のアトミックスケールのメモリを実現しました。

## Japanese Translation: **改善された要約:** 2026年4月11日公開(バージョン v53)の本研究は、単層フッ化グラファイト(CF)を用いたトランジスタ後メモリ architectures を提案することで、「メモリーウォール」と NANDフラッシュ供給危機に対処する。本技術は、sp³混成炭素スカーフレットに対するフッ素原子のコバルント配向のバイステーブル性を二値の自由度として活用している。高レベル計算手法(DLPNO-CCSD(T))により、C-F反転障壁が約 4.8 eV と確認され、300 K における熱・トンネリングに基づくビット反転速度はそれぞれ~10⁻⁶⁵ s⁻¹および~10⁻⁷⁶ s⁻¹であり、ゼロ連続電力消費下での非揮発性データ保持を可能とする。このアプローチは、現在の技術を上回る 5 つの桁(走査プローブプロトタイプで 447 TB/cm²)に及ぶ面密度を実現し、ニアフィールドミッドインフレッツアレイを用いて体積ナノテープアーキテクチャへ拡張した場合には 0.4–9 ZB/cm³の容量に達し、集計されたスループットは 25 PB/s と予測される。これは AI を駆動したストレージ需要に対する革命的な解決策を提供する。 ※注記:改善された要約は、安定性速度および特定のアーキテクチャ階層に関する欠落していた定量的データを統合しつつ、原文に記述されていた高レベルの影響を維持している。

Surelock:Rust 用のデッドロックフリーミューチュエックス | そっか~ニュース