Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Arc

不変な連結リストを使う理由の 1 つは、スレッド間でデータを共有することです。 結局のところ、共有された可変状態は諸悪の根源であり、それを解決する 1 つの方法は、 可変 の部分を永久に消し去ることです。

ただし、私たちのリストはまったくスレッドセーフではありません。スレッドセーフにするには、 参照カウントをアトミックに操作する必要があります。そうしないと、2 つのスレッドが 参照カウントをインクリメントしようとして、片方しか反映されない可能性があります。 すると、リストが早すぎるタイミングで解放されてしまうかもしれません!

スレッドセーフ性を得るには、Arc を使う必要があります。Arc は、参照カウントが アトミックに変更されるという点を除けば、Rc と完全に同一です。 これは必要ない場合には多少のオーバーヘッドになるため、Rust は両方を公開しています。 私たちのリストを作るために必要なのは、Rc への参照をすべて std::sync::Arc に置き換えることだけです。それだけです。スレッドセーフになりました。完了です!

しかし、ここで興味深い疑問が生じます。ある型がスレッドセーフかどうかを、 どうやって知るのでしょうか?うっかり壊してしまうことはあるのでしょうか?

いいえ!Rust ではスレッドセーフ性をうっかり壊すことはできません!

これが成り立つ理由は、Rust がスレッドセーフ性を SendSync という 2 つのトレイトで第一級のものとしてモデル化しているからです。

ある型が Send であるとは、それを別のスレッドへ移動しても安全であることを意味します。ある型が Sync であるとは、 複数のスレッド間で共有しても安全であることを意味します。つまり、T が Sync なら、&T は Send です。ここでいう安全とは、データ競合を引き起こすことが不可能であるという意味です(より一般的な問題である競合状態と混同しないでください)。

これらはマーカートレイトです。これは凝った言い方をすると、インターフェイスをまったく提供しないトレイトということです。あなたは Send であるか、そうでないかのどちらかです。それは単に、 他の API が要求できる性質にすぎません。適切に Send でなければ、 別のスレッドへ送ることは静的に不可能です!すばらしい!

Send と Sync はまた、完全に Send 型と Sync 型で構成されているかどうかに基づいて、 自動的に導出されるトレイトでもあります。これは、自分が Copy 型だけでできている場合にのみ Copy を実装できることに似ていますが、条件を満たしているなら、その実装を自動的に行ってくれるという点が異なります。

ほとんどすべての型は Send かつ Sync です。多くの型が Send であるのは、自分のデータを完全に 所有しているからです。多くの型が Sync であるのは、スレッド間でデータを共有する唯一の方法が、 共有参照の背後に置くことであり、それによってデータが不変になるからです!

しかし、これらの性質に反する特別な型があります。それは内部可変性を持つ型です。 これまで私たちは、実際にはほとんど継承された可変性(別名、外部可変性)だけを扱ってきました。 つまり、値の可変性は、そのコンテナの可変性から継承されるというものです。 言い換えると、非可変な値のあるフィールドを、気分で勝手に変更することはできません。

内部可変性を持つ型はこれに反します。共有参照を通じて値を変更できるようにするからです。 内部可変性には大きく 2 つの分類があります。単一スレッドのコンテキストでのみ機能するセルと、 マルチスレッドのコンテキストで機能するロックです。明らかな理由から、使える場合には セルのほうが安価です。さらに、ロックのように振る舞うプリミティブであるアトミックもあります。

では、これらすべては Rc や Arc とどのような関係があるのでしょうか?実のところ、どちらも 参照カウントのために内部可変性を使っています。さらに悪いことに、この参照カウントは すべてのインスタンス間で共有されます!Rc は単にセルを使っているため、スレッドセーフではありません。 Arc はアトミックを使っているため、スレッドセーフです。もちろん、 型を Arc に入れたからといって、その型を魔法のようにスレッドセーフにできるわけではありません。Arc も他の型と同じように、 スレッドセーフ性を導出できるだけです。

アトミックメモリモデルや、導出されない Send 実装の細かい詳細には、本当に本当に本当に踏み込みたくありません。 言うまでもなく、Rust のスレッドセーフ性の話を深く掘り下げていくと、物事はより複雑になります。 高水準の利用者としては、すべてがただ動くので、実際にはあまり考える必要はありません。