ほとんどのプログラマ (もちろん、私も基本的にその一人です) にとって、同時プログラミングは、関連するデータ構造にロック (ミューテックス) を追加することとほぼ同じです。たとえば、同時実行をサポートするスタックが必要な場合、最も簡単な方法は、シングルスレッド スタックにロックを追加することです。
std::sync::Mutex
。 (複数のスレッドがスタックの所有権を持てるようにするために Arc が追加されています)
<code> <p>use std::sync::{Mutex, Arc};<br><br>#[derive(Clone)]<br>struct ConcurrentStack<T> {<br> inner: Arc<Mutex<Vec<T>>>,<br>}<br><br>impl<T> ConcurrentStack<T> {<br> pub fn new() -> Self {<br> ConcurrentStack {<br> inner: Arc::new(Mutex::new(Vec::new())),<br> }<br> }<br><br> pub fn push(&self, data: T) {<br> let mut inner = self.inner.lock().unwrap();<br> (*inner).push(data);<br> }<br><br> pub fn pop(&self) -> Option<T> {<br> let mut inner = self.inner.lock().unwrap();<br> (*inner).pop()<br> }<br><br>}<br> </p></code>
コードはシングルスレッド バージョンとほぼ同じであるため、記述するのが非常に便利であり、明らかに利点です。シングルスレッド バージョンを作成し、データ構造にロックを追加し、必要に応じて (Rust では基本的に自動で) ロックを取得および解放するだけです。 ######だから問題は何ですか?まず第一に、ロックの取得と解放を忘れる可能性があるという事実 (Rust のおかげで、Rust ではこのようなことはほとんど起こりません) のほかに、デッドロックの問題 (ダイニング哲学者問題) に直面する可能性があります。また、言うまでもなく、一部の優先度の低いタスクは、長時間にわたって優先度の高いタスクのリソースを占有する可能性があります (ロックが最初に行われるため)。スレッドの数が比較的多い場合、ほとんどの時間は同期に費やされます (取得するロック)、パフォーマンスが非常に低下します。大量の読み取りと時折の書き込みを行う同時データベースを考えてみましょう。ロックを使用して処理すると、データベースに更新がない場合でも、2 回の読み取りごとに同期が必要になり、コストがかかりすぎます。
ロックフリーの同時実行性
の型は、アトミック ポインターなどの CAS 操作を提供します。
std::sync::atomic::AtomicPtr
<pre class="brush:php;toolbar:false"><code>
<p>pub fn compare_and_swap(<br> &self,<br> current: *mut T,<br> new: *mut T,<br> order: Ordering<br>) -> *mut T<br>
</p></code></pre>
(ここでは、順序については気にしないでください。つまり、無視してください。
###取得する###
、
###リリース###
、
)ロックフリー スタック (単純なバージョン)
<pre class="brush:php;toolbar:false"><code>
<p>#![feature(box_raw)]<br><br>use std::ptr::{self, null_mut};<br>use std::sync::atomic::AtomicPtr;<br>use std::sync::atomic::Ordering::{Relaxed, Release, Acquire};<br><br>pub struct Stack<T> {<br> head: AtomicPtr<Node<T>>,<br>}<br><br>struct Node<T> {<br> data: T,<br> next: *mut Node<T>,<br>}<br><br>impl<T> Stack<T> {<br> pub fn new() -> Stack<T> {<br> Stack {<br> head: AtomicPtr::new(null_mut()),<br> }<br> }<br><br> pub fn pop(&self) -> Option<T> {<br> loop {<br> // 快照<br> let head = self.head.load(Acquire);<br><br> // 如果栈为空<br> if head == null_mut() {<br> return None<br> } else {<br> let next = unsafe { (*head).next };<br><br> // 如果现状较快照并没有发生改变<br> if self.head.compare_and_swap(head, next, Release) == head {<br><br> // 读取内容并返回<br> return Some(unsafe { ptr::read(&(*head).data) })<br> }<br> }<br> }<br> }<br><br> pub fn push(&self, t: T) {<br> // 创建node并转化为*mut指针<br> let n = Box::into_raw(Box::new(Node {<br> data: t,<br> next: null_mut(),<br> }));<br> loop {<br> // 快照<br> let head = self.head.load(Relaxed);<br><br> // 基于快照更新node<br> unsafe { (*n).next = head; }<br><br> // 如果在此期间,快照仍然没有过时<br> if self.head.compare_and_swap(head, n, Release) == head {<br> break<br> }<br> }<br> }<br>}<br>
</p></code></pre>
ポップでもプッシュでも考え方は同じであることがわかります。最初にスナップショットをポップするか、またはをプッシュして、元のデータを CAS に置き換えてみます。スナップショットとデータが等しい場合は、この期間中に書き込みが実行されなかったことを意味するため、更新は成功します。データに一貫性がない場合は、この期間中に他のスレッドがデータを変更したため、再起動する必要があることを意味します。これはロックフリーのスタックです。すべてが完了したようです!
Java または他のプログラミング言語を GC で使用している場合は、多くの作業を行ったことがあるかもしれません。今の問題は、GC のない Rust のような言語では、ポップで
return Some(unsafe { ptr::read(&(*head).data) })
以上がJava ロック同時実行性、ロックフリー同時実行性、および CAS サンプル分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。