Inhaltsverzeichnis
Gesperrte Parallelität
无锁并发
CAS(compare and swap)原语
无锁栈(天真版)
内存释放
🎜CAS (vergleichen und austauschen) primitiv🎜
🎜Sperrfreier Stapel (naive Version)🎜
🎜Memory Release🎜
Heim Java javaLernprogramm Java-Sperren-Parallelität, sperrenfreie Parallelität und CAS-Beispielanalyse

Java-Sperren-Parallelität, sperrenfreie Parallelität und CAS-Beispielanalyse

May 23, 2023 pm 01:34 PM
java cas

Gesperrte Parallelität

Für die meisten Programmierer (natürlich bin ich im Grunde einer von ihnen) ist gleichzeitiges Programmieren fast gleichbedeutend mit dem Hinzufügen einer Sperre (Mutex) zur relevanten Datenstruktur. Wenn wir beispielsweise einen Stapel benötigen, der Parallelität unterstützt, ist es am einfachsten, einem Single-Threaded-Stack eine Sperre hinzuzufügen std::sync::Mutex . (Arc wird hinzugefügt, um mehreren Threads den Besitz des Stapels zu ermöglichen) 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>
Nach dem Login kopieren

代码写起来十分方便,因为它几乎与单线程版本相同,这很明显是一个好处。只需要按照单线程的版本写完,然后给数据结构加上锁,然后在必要的时候获取和释放(在Rust中基本上是自动的)锁即可。

那么问题是什么呢?首先不谈你可能会忘记获取和释放锁(这一点要感谢Rust,在Rust中几乎不可能发生),你可能会面临死锁的问题(哲学家就餐问题)。然后也不谈一些低优先级的任务可能会长期抢占高优先级任务的资源(因为锁是第一位的),当线程数量比较大的时候,大部分的时间都被用在了同步上(等待锁能被获取),性能就会变得非常差。考虑一个大量读取而偶尔写入的并发数据库,如果用锁去处理,即使数据库没有任何更新,每两个读之间也需要做一次同步,代价太大!

无锁并发

于是,大量计算机科学家和程序员就把目光转向了无锁(lock-free)并发。无锁对象:如果一个共享对象保证了无论其他线程做何种操作,总有一些线程会在有限的系统操作步骤后完成一个对其的操作  Her91  。也就是说,至少有一个线程对其操作会取得成效。使用锁的并发明显就不属于这一范畴:如果获取了锁的线程被延迟,那么这段时间里没有任何线程能够完成任何操作。极端情况下如果出现了死锁,那么没有任何线程能够完成任何操作。

CAS(compare and swap)原语

那大家可能会好奇,无锁并发要怎么实现呢?有没有例子呢?在此之前让我们先看一下一个公认的在无锁并发中非常重要的原子原语:CAS。CAS的过程是用指定值去比较一储存值,只有当他们相同时,才会修改储存值为新的指定值。CAS是个原子操作(由处理器支持,比如x86的compare and exchange (CMPXCHG)),该原子性保证了如果其他线程已经改变了储存值,那么写入就会失败。Rust标准库中的  std::sync::atomic  中的类型就提供了CAS操作,比如原子指针  std::sync::atomic::AtomicPtr

<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>
Nach dem Login kopieren

(在这里,不用纠结ordering是什么,也就是说请尽管忽略忽略  Acquire  ,   Release  ,   Relaxed  )

无锁栈(天真版)

<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>
Nach dem Login kopieren

我们可以看到,无论是pop还是push思路都是一样的:先在快照上pop或者是push,然后尝试用CAS替换原来的数据。如果快照和数据相等,就意味着在此期间没有执行写操作,因此更新就能成功。如果数据不一致,则表明在此期间有其他线程对其进行了修改,需要重新开始。这就是一个无锁的栈。似乎一切都已经大功告成了!

内存释放

如果你正在使用Java或其他带有GC的编程语言,你可能已经完成了大量的工作。现在的问题在于,在Rust这种没有GC的语言中,pop中

return Some(unsafe { ptr::read(&(*head).data) })
Nach dem Login kopieren

并没有人去释放  headrrreee

Der Code ist sehr bequem zu schreiben, da er fast mit der Single-Thread-Version identisch ist, was offensichtlich ein Vorteil ist. Schreiben Sie einfach die Single-Threaded-Version, fügen Sie eine Sperre zur Datenstruktur hinzu und erwerben Sie dann die Sperre (im Grunde genommen automatisch in Rust) und geben Sie sie bei Bedarf frei. 🎜🎜Was ist also das Problem? Abgesehen von der Tatsache, dass Sie möglicherweise vergessen, die Sperre zu erwerben und freizugeben (was in Rust dank Rust fast unmöglich ist), können Sie zunächst mit dem Problem eines Deadlocks (dem Dining Philosopher Problem) konfrontiert werden. Ganz zu schweigen davon, dass einige Aufgaben mit niedriger Priorität möglicherweise lange Zeit die Ressourcen von Aufgaben mit hoher Priorität beanspruchen (da Sperren zuerst kommen). Wenn die Anzahl der Threads relativ groß ist, wird die meiste Zeit für die Synchronisierung aufgewendet (Warten auf). die zu erwerbende Sperre), wird die Leistung sehr schlecht. Stellen Sie sich eine gleichzeitige Datenbank mit einer großen Anzahl von Lesevorgängen und gelegentlichen Schreibvorgängen vor. Wenn zur Verarbeitung Sperren verwendet werden, ist eine Synchronisierung zwischen jeweils zwei Lesevorgängen erforderlich, was zu kostspielig ist. 🎜🎜🎜Sperrenfreie Parallelität🎜🎜🎜Daher richteten viele Informatiker und Programmierer ihre Aufmerksamkeit auf die sperrenfreie Parallelität. Sperrfreies Objekt: Wenn ein gemeinsam genutztes Objekt garantiert, dass unabhängig davon, was andere Threads tun, nach einer begrenzten Anzahl von Systemoperationsschritten immer ein Thread eine Operation darauf abschließt. Her91 . Mit anderen Worten: Mindestens ein Thread erzielt Ergebnisse aus seiner Operation. Parallelität mit Sperren fällt offensichtlich nicht in diese Kategorie: Wenn der Thread, der die Sperre erhalten hat, verzögert ist, kann in dieser Zeit kein Thread irgendeinen Vorgang abschließen. Im Extremfall, wenn ein Deadlock auftritt, kann kein Thread einen Vorgang abschließen. 🎜

🎜CAS (vergleichen und austauschen) primitiv🎜

🎜Dann sind Sie vielleicht neugierig, wie man eine sperrenfreie Parallelität erreicht? Gibt es Beispiele? Werfen wir zuvor einen Blick auf ein atomares Grundelement, das für die sperrenfreie Parallelität als sehr wichtig gilt: CAS. Der Prozess von CAS besteht darin, einen gespeicherten Wert mit einem angegebenen Wert zu vergleichen. Nur wenn sie gleich sind, wird der gespeicherte Wert auf den neuen angegebenen Wert geändert. CAS ist eine atomare Operation (die vom Prozessor unterstützt wird, z. B. x86s Compare and Exchange (CMPXCHG)). Diese Atomizität garantiert, dass der Schreibvorgang fehlschlägt, wenn andere Threads den gespeicherten Wert geändert haben. in der Rust-Standardbibliothek std::sync::atomic Die Typen in stellen CAS-Operationen bereit, z. B. atomare Zeiger std::sync::atomic::AtomicPtr 🎜rrreee🎜(Machen Sie sich hier keine Gedanken darüber, was Bestellung ist, das heißt, bitte ignorieren Sie es einfach. Erhalten , Freigabe , Entspannt )🎜

🎜Sperrfreier Stapel (naive Version)🎜

rrreee🎜Wir können sehen, dass die Idee dieselbe ist, egal ob Pop oder Push: Pop oder Push zuerst auf dem Snapshot und dann versuchen, ihn zu ersetzen es mit CAS-Originaldaten. Wenn der Snapshot und die Daten gleich sind, bedeutet dies, dass in diesem Zeitraum keine Schreibvorgänge durchgeführt wurden, sodass die Aktualisierung erfolgreich ist. Wenn die Daten inkonsistent sind, bedeutet dies, dass sie in diesem Zeitraum von anderen Threads geändert wurden und erneut gestartet werden müssen. Dies ist ein sperrenfreier Stapel. Es scheint, dass alles erledigt ist! 🎜

🎜Memory Release🎜

🎜Wenn Sie Java oder andere Programmiersprachen mit GC verwenden, haben Sie möglicherweise viel Arbeit geleistet. Das Problem ist nun, dass in einer Sprache wie Rust ohne GC niemand den Pop 🎜rrreee🎜 veröffentlicht Kopf , das ist ein Speicherleck! Hey, es scheint, dass sperrenfreie Parallelität nicht einfach ist. 🎜

Das obige ist der detaillierte Inhalt vonJava-Sperren-Parallelität, sperrenfreie Parallelität und CAS-Beispielanalyse. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn

Heiße KI -Werkzeuge

Undresser.AI Undress

Undresser.AI Undress

KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover

AI Clothes Remover

Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool

Undress AI Tool

Ausziehbilder kostenlos

Clothoff.io

Clothoff.io

KI-Kleiderentferner

Video Face Swap

Video Face Swap

Tauschen Sie Gesichter in jedem Video mühelos mit unserem völlig kostenlosen KI-Gesichtstausch-Tool aus!

Heiße Werkzeuge

Notepad++7.3.1

Notepad++7.3.1

Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version

SublimeText3 chinesische Version

Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1

Senden Sie Studio 13.0.1

Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6

Dreamweaver CS6

Visuelle Webentwicklungstools

SublimeText3 Mac-Version

SublimeText3 Mac-Version

Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Perfekte Zahl in Java Perfekte Zahl in Java Aug 30, 2024 pm 04:28 PM

Leitfaden zur perfekten Zahl in Java. Hier besprechen wir die Definition, Wie prüft man die perfekte Zahl in Java?, Beispiele mit Code-Implementierung.

Weka in Java Weka in Java Aug 30, 2024 pm 04:28 PM

Leitfaden für Weka in Java. Hier besprechen wir die Einführung, die Verwendung von Weka Java, die Art der Plattform und die Vorteile anhand von Beispielen.

Smith-Nummer in Java Smith-Nummer in Java Aug 30, 2024 pm 04:28 PM

Leitfaden zur Smith-Zahl in Java. Hier besprechen wir die Definition: Wie überprüft man die Smith-Nummer in Java? Beispiel mit Code-Implementierung.

Fragen zum Java Spring-Interview Fragen zum Java Spring-Interview Aug 30, 2024 pm 04:29 PM

In diesem Artikel haben wir die am häufigsten gestellten Fragen zu Java Spring-Interviews mit ihren detaillierten Antworten zusammengestellt. Damit Sie das Interview knacken können.

Brechen oder aus Java 8 Stream foreach zurückkehren? Brechen oder aus Java 8 Stream foreach zurückkehren? Feb 07, 2025 pm 12:09 PM

Java 8 führt die Stream -API ein und bietet eine leistungsstarke und ausdrucksstarke Möglichkeit, Datensammlungen zu verarbeiten. Eine häufige Frage bei der Verwendung von Stream lautet jedoch: Wie kann man von einem Foreach -Betrieb brechen oder zurückkehren? Herkömmliche Schleifen ermöglichen eine frühzeitige Unterbrechung oder Rückkehr, aber die Stream's foreach -Methode unterstützt diese Methode nicht direkt. In diesem Artikel werden die Gründe erläutert und alternative Methoden zur Implementierung vorzeitiger Beendigung in Strahlverarbeitungssystemen erforscht. Weitere Lektüre: Java Stream API -Verbesserungen Stream foreach verstehen Die Foreach -Methode ist ein Terminalbetrieb, der einen Vorgang für jedes Element im Stream ausführt. Seine Designabsicht ist

Zeitstempel für Datum in Java Zeitstempel für Datum in Java Aug 30, 2024 pm 04:28 PM

Anleitung zum TimeStamp to Date in Java. Hier diskutieren wir auch die Einführung und wie man Zeitstempel in Java in ein Datum konvertiert, zusammen mit Beispielen.

Java -Programm, um das Kapselvolumen zu finden Java -Programm, um das Kapselvolumen zu finden Feb 07, 2025 am 11:37 AM

Kapseln sind dreidimensionale geometrische Figuren, die aus einem Zylinder und einer Hemisphäre an beiden Enden bestehen. Das Volumen der Kapsel kann berechnet werden, indem das Volumen des Zylinders und das Volumen der Hemisphäre an beiden Enden hinzugefügt werden. In diesem Tutorial wird erörtert, wie das Volumen einer bestimmten Kapsel in Java mit verschiedenen Methoden berechnet wird. Kapselvolumenformel Die Formel für das Kapselvolumen lautet wie folgt: Kapselvolumen = zylindrisches Volumenvolumen Zwei Hemisphäre Volumen In, R: Der Radius der Hemisphäre. H: Die Höhe des Zylinders (ohne die Hemisphäre). Beispiel 1 eingeben Radius = 5 Einheiten Höhe = 10 Einheiten Ausgabe Volumen = 1570,8 Kubikeinheiten erklären Berechnen Sie das Volumen mithilfe der Formel: Volumen = π × R2 × H (4

Gestalten Sie die Zukunft: Java-Programmierung für absolute Anfänger Gestalten Sie die Zukunft: Java-Programmierung für absolute Anfänger Oct 13, 2024 pm 01:32 PM

Java ist eine beliebte Programmiersprache, die sowohl von Anfängern als auch von erfahrenen Entwicklern erlernt werden kann. Dieses Tutorial beginnt mit grundlegenden Konzepten und geht dann weiter zu fortgeschrittenen Themen. Nach der Installation des Java Development Kit können Sie das Programmieren üben, indem Sie ein einfaches „Hello, World!“-Programm erstellen. Nachdem Sie den Code verstanden haben, verwenden Sie die Eingabeaufforderung, um das Programm zu kompilieren und auszuführen. Auf der Konsole wird „Hello, World!“ ausgegeben. Mit dem Erlernen von Java beginnt Ihre Programmierreise, und wenn Sie Ihre Kenntnisse vertiefen, können Sie komplexere Anwendungen erstellen.

See all articles