Rust 聖書分析の研究 - Rust Learn-13 (並行)
同時性
並列プログラミングとは、プログラムのさまざまな部分が互いに独立して実行されることを意味し、並列プログラミングとは、プログラムのさまざまな部分が同時に実行されることを意味します. コンピュータがマルチプロセッサをますます活用するにつれて、これらの 2 つの概念がより重要になります.
実行されたプログラムのコードはプロセス (プロセス) で実行され、オペレーティング システムは複数のプロセスを管理する役割を果たします。プログラム内では、複数の独立した部分を同時に実行することもできます。これらの別々の部分を実行する機能は、スレッドと呼ばれます
新しいスレッドを作成
必要な型は FnOnce であるため、spawn メソッドを介して新しいスレッドを作成できます。そのため、クロージャを直接渡すことができます。
use std::thread;
fn main() {
thread::spawn(|| {
println!("new thread");
});
println!("origin thread");
}
join を使用して、すべてのスレッドが終了するのを待ちます
join メソッドは、スレッドの実行が完了し、スレッドが実行されていることを確認するためにブロックするのに役立ちます。これは、スレッドが実行に適していること、またはスレッドが実行される順序が適切であることを保証できないためです。
use std::thread;
fn main() {
let t1 = thread::spawn(|| {
println!("new thread");
});
println!("origin thread");
t1.join();
println!("finish");
}
スレッドは環境の所有権を取得します
私たちのスレッドはクロージャーと一緒に使用されることが多いため、move キーワードを使用して環境で取得した値の所有権を取得し、これらの値の所有権をあるスレッドから別のスレッドに転送することも自然です。
次のように、このプログラムには問題があります。x の所有権はまだメイン スレッドにあるため、x の値をスレッドで出力できないため、move を使用して所有権を譲渡する必要があります。
use std::thread;
fn main() {
let x = 5;
let t1 = thread::spawn(|| {
println!("new thread");
println!("{}", x);
});
println!("origin thread");
t1.join();
println!("finish");
}
改訂:
use std::thread;
fn main() {
let x = 5;
let t1 = thread::spawn(move || {
println!("new thread");
println!("{}", x);
});
println!("origin thread");
t1.join();
println!("finish");
}
メッセージパッシングによるデータの受け渡し
安全な並行性を確保するための一般的な方法は、スレッドまたはアクターがデータを含むメッセージを送信することによって相互に通信するメッセージ パッシングです。メッセージ パッシングの同時実行性を実現するために、Rust 標準ライブラリはチャネルの実装を提供します。チャネルは、あるスレッドから別のスレッドへのデータの送信を表す一般的なプログラミングの概念です。
チャネルを作成
標準ライブラリに mpsc in sync を導入し、チャネル メソッドを使用してチャネルを構築する必要があります。
use std::thread;
use std::sync::mpsc;
fn main() {
let (sender, getter) = mpsc::channel();
}
メッセージの送信|受信
Send: send メソッド、Result<T, E>
型をため、受信側が破棄された場合、値を送信するターゲットがないため、送信操作はエラーを返します
Accept: recv メソッドは、値が返されるまでメイン スレッドの実行をブロックしますチャネルから受信します。値が送信されると、recv はそれを Result<T, E> で返します。チャネルの送信者が閉じられると、recv は新しい値が来ないことを示すエラーを返します。
use std::thread;
use std::sync::mpsc;
fn main() {
let (sender, getter) = mpsc::channel();
let msg = String::from("nihao");
sender.send(msg).unwrap_or(());
let t1 = thread::spawn(move||{
let g_msg = getter.recv().unwrap();
println!("{}", g_msg);
});
}
try_recv: はブロックせず、代わりにすぐに 1 を返しますResult<T, E>
。Ok 値には利用可能な情報が含まれ、Err 値は現時点でメッセージがないことを示します。try_recv を使用すると、スレッドがメッセージを待っている間に他の作業を行う場合に便利です。try_recv を頻繁に呼び出し、メッセージが利用可能になったときに処理し、残りの時間は再度チェックするまで他の作業を行うループを作成できます。
recv への暗黙の呼び出し
recv 関数は明示的に呼び出されなくなりました: 代わりに rx は反復子として扱われます。受け取った値ごとに、それを出力します。チャネルが閉じられると、イテレータも終了します。
for received in rx {
println!("Got: {}", received);
}
共有状態の同時実行
もう 1 つの方法は、複数のスレッドが同じ共有データを所有できるようにすることです。
ある意味では、任意のプログラミング言語のチャネルは単一の所有権に似ており、値がチャネルに転送されると、それは使用できなくなります。共有メモリは、複数の所有権に似ています。複数のスレッドが同じメモリ位置に同時にアクセスできます。
ミューテックスを使用する ミューテックス
ミューテックスは、相互排除の頭字語です。つまり、常に 1 つのスレッドだけが特定のデータにアクセスできるようにします。ミューテックス内のデータにアクセスするには、スレッドはまず、ミューテックスのロックを取得して、データにアクセスしたいことを示す必要があります。ロックは、データへの排他的アクセス権を持つユーザーを記録するミューテックスの一部であるデータ構造です。したがって、ミューテックスは、ロック システムを通じてデータを保護するものとして説明します。
だから私たちのステップ:
- データを使用する前にロックの取得を試みます。
- ミューテックスによって保護されたデータを処理した後、他のスレッドがロックを取得できるように、データのロックを解除する必要があります。
ミューテックスを適切に管理することは驚くほど複雑です。そのため、多くの人がチャネルに熱心です。しかし、Rust では、型システムと所有権のおかげで、ロックとロック解除を間違えることはありません。
ミューテックスの作成
let m = Mutex::new(0);
共有ミューテックス
ここでは、lock を使用してロックを取得し、逆参照して値を変更できます。
use std::thread;
use std::sync::{
mpsc, Mutex};
fn main() {
let m = Mutex::new(0);
{
let mut num = m.lock().unwrap();
*num = 6;
}
println!("{:?}", m);
Arc<T>
アトミック参照カウント
似ていますRc<T>
が、Arc はアトミックであり、並列環境で安全に使用できます.Arc を使用すると、スレッド間で値を安全に共有できます
use std::thread;
use std::sync::{
Arc, mpsc, Mutex};
fn main() {
let m = Arc::new(Mutex::new(0));
let counter = Arc::clone(&m);
let handle = thread::spawn(move || {
let mut lock = counter.lock().unwrap();
*lock = 100;
});
handle.join().unwrap();
println!("{:?}", *m.lock().unwrap());
}
Sync および Send トレイトを使用したスケーラブルな同時実行
Rust の並行性モデルの興味深い側面は、言語自体が並行性についてほとんど知らないことです。これまでに説明したほとんどすべては、言語自体ではなく、標準ライブラリに属しています。言語は同時実行関連のインフラストラクチャを提供する必要がないため、同時実行ソリューションは標準ライブラリや言語によって制限されません。独自に作成することも、他の人が作成した同時実行関数を使用することもできます。
ただし、言語には 2 つの同時実行の概念が組み込まれています:std::marker
inSync
とSend trait
.
Send を介してスレッド間で所有権を転送できます
- Send トレイト タイプの値が実装されている限り、スレッド間で所有権を譲渡できます。
- ほとんどすべての Rust タイプは Send です
Rc<T>
送信は実装されていません- 生のポインタは送信されません
同期はマルチスレッドアクセスを可能にします
- Sync マーカー トレイトは、Sync を実装する型がその値への参照を複数のスレッドで安全に保持できることを示します (任意の型 T について、&T (T への不変参照) が Send の場合、T は Sync です)。
- 基本的なタイプは同期です
Rc<T>
同期しない
注: Send と Sync を手動で実装するのは安全ではありません
Send と Sync の型で構成される型は自動的に Send と Sync になるため、通常は Send と Sync トレイトを手動で実装する必要はありません。これらはマーカー トレイトであるため、メソッドを実装する必要さえありません。それらは、並行性に関連する不変性を強制するためにのみ使用されます。