Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

$ cat .profile GIT_AUTHOR_NAME=Florian Gilcher GIT_AUTHOR_EMAIL=florian@rustfest.eu TWITTER_HANDLE=argorak GITHUB_HANDLE=skade

Slide 3

Slide 3 text

• Backend-Entwickler

Slide 4

Slide 4 text

• Community-Mensch • Rust UG Berlin • Vorstand Ruby Berlin e.V.

Slide 5

Slide 5 text

• Mitglied des globalen Rust commu- nity teams • Organisator eurucamp/jruby- conf.eu/RustFest/isleofruby

Slide 6

Slide 6 text

Rust

Slide 7

Slide 7 text

• Erfunden von Graydon Hoare • Basiert auf linearen Typen • Regionen-basiertes Speicherman- agement

Slide 8

Slide 8 text

• Entwickelt von Mozilla und der Community • Finanziert von Mozilla • Jetzt schon Firefox! (Firefox 56) • Insbesondere für parsing (URL und MP4-Metadata)

Slide 9

Slide 9 text

Ziele von Rust • Speichersicherheit ohne Garbage Collection • Unterstützung bei Nebenläufigkeit • (Erwartbare) Geschwindigkeit

Slide 10

Slide 10 text

Sicher • statisches Typsystem mit Typin- ferenz • Keine Null-Pointer • Kein Pointer-aliasing

Slide 11

Slide 11 text

Nebenläufigkeit • Mutabilität als Konzept erster Klasse • Keine konkurrierenden Zugriffe auf mutable Daten • Wichtige Basistypen mitgeliefert

Slide 12

Slide 12 text

Geschwindigkeit • Keine versteckten Allokationen • Erwartbare Deallokationen • Abstraktionen ohne Laufzeitkosten

Slide 13

Slide 13 text

Das Ziel ist C • Kontrollierbares Memory-Layout • Kann C-ABI-kompatible Biblio- theken erstellen • Keine Laufzeit • optionale unsichere Subsprache (z.B. für FFI)

Slide 14

Slide 14 text

"Concurrency without Fear"?

Slide 15

Slide 15 text

Fear

Slide 16

Slide 16 text

Nebenläufige und parallele Programme gelten als notorisch schwer zu schreiben, fehleranfällig und schwer zu debuggen.

Slide 17

Slide 17 text

Bugs • Non-deterministisch • Wegen der kurzen Zeitspannen sel- ten • Nicht lokal

Slide 18

Slide 18 text

Sequenziell 1 + 1 + 1 = 3

Slide 19

Slide 19 text

Parallel 1 + 1 + 1 = 3, ausser in manchen seltenen Fällen

Slide 20

Slide 20 text

Problemquellen

Slide 21

Slide 21 text

Data races • Mehrere Teile des Programms ändern ohne Synchronisation Daten, auf die sie alle Zugriff haben

Slide 22

Slide 22 text

Resource races • Mehrere Teile eines Programms entziehen sich gegenseitig Zugriff auf Resourcen, die sie aber alle benötigen

Slide 23

Slide 23 text

Wir werden uns hier mehr mit data races beschäftigen.

Slide 24

Slide 24 text

• Mehrere Teile des Programms ändern ohne Synchronisation Daten, auf die sie alle Zugriff haben

Slide 25

Slide 25 text

Probleme • Teilen von Daten (Sharing) • Verändern von Daten (Mutation)

Slide 26

Slide 26 text

Credo Mutables teilen von Daten ist böse.

Slide 27

Slide 27 text

Achtung Nebenläufigkeit ist nicht nötig, um hiermit Bugs zu produzieren!

Slide 28

Slide 28 text

fn main() { let mut v = vec![1; 10]; let element = &v[9] as *const i32; v.push(1); println!("{}", unsafe { *element }); }

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

fn main() { let mut v = vec![1; 10]; let element = &v[3]; v.push(1); println!("{}", element); }

Slide 34

Slide 34 text

error[E0502]: cannot borrow `v` as mutable because it is also bo –> vec_crash.rs:4:5 | 3 | let element = &v[3]; | - immutable borrow occurs here 4 | v.pop(); | ^ mutable borrow occurs here 5 | println!("{}", element ); 6 | } | - immutable borrow ends here error: aborting due to previous error

Slide 35

Slide 35 text

Praktische Lösungen

Slide 36

Slide 36 text

Keine Mutabilität (z.B. Haskell) "Mutability is the root of all evil"

Slide 37

Slide 37 text

In funktionalen Programmiersprachen sind Daten generell nicht mutabel.

Slide 38

Slide 38 text

Beispiel Das hinzufügen eines Elements zu einer Liste gibt stets eine neue Liste zurück.

Slide 39

Slide 39 text

main = do let list = [1,2,3] print list let list2 = list ++ [4] print list print list2

Slide 40

Slide 40 text

Solche Ansätze verlassen sich auf Optimierungen, zum Beispiel structural sharing.

Slide 41

Slide 41 text

Share-nothing Keine 2 konkurrierenden Programmeinheiten dürfen gleichzeitig Zugriff auf Daten haben. Teilen funktioniert über Kopieren.

Slide 42

Slide 42 text

Beispiel: Erlang Erlang erlaubt weder die Mutation von Daten, noch deren Sharing. Werden Daten zu anderen Prozessteilen geschickt, werden sie immer kopiert.

Slide 43

Slide 43 text

Beide Strategien können ohne Unterstützung der verwendeten Programmiersprache verwendet werden (und sind empfohlen), aber natürlich mit Sprachunterstützung viel leichter umzusetzen.

Slide 44

Slide 44 text

Sie sind auch zu empfehlen, wenn garnicht nebenläufig gearbeitet wird!

Slide 45

Slide 45 text

Schwierigkeiten Schwierigkeiten schaffen Daten, an denen ein Zustand hängt.

Slide 46

Slide 46 text

Filepointer Filepointer sind Daten mit einem impliziten Zustand. (offen, geschlossen)

Slide 47

Slide 47 text

import System.IO main = do inFile <- openFile "foo" ReadMode contents <- hGetLine inFile putStrLn contents hClose inFile contents <- hGetLine inFile putStrLn contents hClose inFile

Slide 48

Slide 48 text

test read_file: foo: hGetLine: illegal operation (handle is closed)

Slide 49

Slide 49 text

-module(read_file). -export([read_text_file/1]). read_text_file(Filename) -> {ok, IoDevice} = file:open(Filename, [read]), read_text(IoDevice), file:close(IoDevice), read_text(IoDevice). read_text(IoDevice) -> case file:read_line(IoDevice) of {ok, Line} -> io:format("~s", [Line]), ok; eof -> ok end.

Slide 50

Slide 50 text

1> c(read_file). {ok,read_file} 2> read_file:read_text_file("foo"). test ** exception error: no case clause matching {error,terminated} in function read_file:read_text/1 (read_file.erl, line 11)

Slide 51

Slide 51 text

Können wir auch solche Patterns unterbinden?

Slide 52

Slide 52 text

Ownership

Slide 53

Slide 53 text

• Alle Daten haben genau einen Be- sitzer • Besitz kann abgegeben werden • Wenn Daten das Ende eines Scopes erreichen, werden sie zerstört

Slide 54

Slide 54 text

use std::fs::File; use std::io::Write; fn main() { let file = File::open("test") .expect("Unable to open file, bailing!"); take_and_write_to_file(file); // take_and_write_to_file(file); // ^^ Illegal } fn take_and_write_to_file(mut file: File) { writeln!(file, "{}", "Hello Konstanz!"); }

Slide 55

Slide 55 text

• Zugriff kann verliehen werden (mu- tabel und immutabel) • Einmalig mutabel • Oder mehrfach immutabel • Exklusiv: mutable or immutabel, niemals beides

Slide 56

Slide 56 text

use std::fs::File; use std::io::Write; fn main() { let mut file = File::open("test") .expect("Unable to open file, bailing!"); write_to_file(&mut file); write_to_file(&mut file); } fn write_to_file(file: &mut File) { writeln!(file, "{}", "Hello Konstanz!"); }

Slide 57

Slide 57 text

• Ownership erlaubt uns share- nothing • Borrowing erlaubt uns sharing, aber ohne Mutation • Mutables borrowing garantiert exk- lusive Mutation

Slide 58

Slide 58 text

Wir können also jederzeit von einem der Systeme ins andere wechseln, solange wir nur nach einem arbeiten.

Slide 59

Slide 59 text

Zwischenfazit Rust minimiert gefährliche und versteckt problematische Patterns in sequentiellem Code.

Slide 60

Slide 60 text

Wollten wir nicht über Concurrency reden?

Slide 61

Slide 61 text

Option 1: Threads • Berechnungen werden nebenläufig ausgeführt • Ein Scheduler verwaltet die Ausführung • Haben einen eigenen Stack • Teilen den Heap • Können einzeln blockieren

Slide 62

Slide 62 text

Vorteile • Folgen im Einzelnen einem (relativ) linearen Modell • Auf (fast) allen Betriebssystem nativ unterstützt • Threads haben einen eigenen Spe- icherbereich (thread-local storage) • Stacks können zum debuggen be- nutzt werden

Slide 63

Slide 63 text

Nachteile • Jeder Thread braucht Platz für einen Stack • Es können recht einfach Synchro- nisierungsbugs auftreten

Slide 64

Slide 64 text

Populäre Threaded-Systeme • Apache • Varnish • Apache Tomcat

Slide 65

Slide 65 text

• Jede Aktion ist ein "Event" • Das System hält eine Liste zu bear- beitender Events • Eine große Schleife arbeitet diese Events nach und nach ab • Events können weitere Events auslösen • Aktionen haben keine Verbindung über den Stack

Slide 66

Slide 66 text

Vorteile • Eine Aktion zu einer Zeit: weniger Synchronisierungsprobleme • Blockaden werden durch Einreihen ans Ende der Queue abgebildet • Blockaden sind sehr günstig

Slide 67

Slide 67 text

Nachteile • Aktionen, die den ausführenden Thread doch blockieren, blockieren alles • Schwer zu folgen, da Reihenfolge selbst herzustellen ist

Slide 68

Slide 68 text

Populäre Evented-Systeme • Java NIO ("new I/O") • V8/node.js • nginx • BEAM (Erlang-VM)

Slide 69

Slide 69 text

Option 3: Beides Viel Spass!

Slide 70

Slide 70 text

Populäre Systeme mit beidem • Datenbanken, z.B. Elasticsearch • Spiele • Fast jede moderne Desktop-App

Slide 71

Slide 71 text

Verschärfte Probleme Die oben beschriebenen Probleme werden schwieriger, wenn die Abfolge von Aktionen nicht mehr garantiert ist.

Slide 72

Slide 72 text

Dateien: Operationen • Öffnen von Dateien • Schließen von Dateien • Lesen von Dateien

Slide 73

Slide 73 text

Aufgabe Öffne eine Datei, lese 2 Zeilen, schließe die Datei wieder.

Slide 74

Slide 74 text

Abhängigkeiten Wir können nur aus offenen Dateien lesen.

Slide 75

Slide 75 text

Schließen von Dateien unterbindet weiteres Lesen.

Slide 76

Slide 76 text

Unabhängig Welcher Programmteil zuerst welche Zeile liest, ist egal.

Slide 77

Slide 77 text

Send und Sync

Slide 78

Slide 78 text

Send Ein Wert kann zwischen Threads abgegeben werden. Der sendende Thread verliert den Zugriff. Der Zugriff bleibt exklusiv.

Slide 79

Slide 79 text

Der Compiler stellt "Send" automatisch fest, wenn alle inneren Werte Send sind.

Slide 80

Slide 80 text

Insbesondere nicht Send sind alle Werte, die geteilte, mutable Daten enthalten.

Slide 81

Slide 81 text

Sync Ein Wert kann zwischen Threads geteilt werden. Der sendende Thread behält den Zugriff. Der Zugriff wird geteilt.

Slide 82

Slide 82 text

Der Compiler stellt "Sync" automatisch fest, wenn alle inneren Werte Sync sind.

Slide 83

Slide 83 text

Insbesondere nicht Send sind alle Werte, die geteilte, mutable Daten enthalten.

Slide 84

Slide 84 text

Send und Sync sind elegant Sie sagen nichts spezifisches über die verwendete Technologie aus. Sie erlauben aber spezifisches Arbeiten mit einer Technologie.

Slide 85

Slide 85 text

Typ-Beispiele

Slide 86

Slide 86 text

std::rc::Rc Ein Pointer, der mehrfaches, zur Laufzeit geprüften, immutablen Zugriff auf Daten erlaubt.

Slide 87

Slide 87 text

std::rc::Rc Ist nicht Send und nicht Sync

Slide 88

Slide 88 text

std::sync::Arc Ein Pointer, der mehrfachen, zur Laufzeit geprüften, immutablen Zugriff auf Daten erlaubt. Synchronisiert seinen internen Zähler.

Slide 89

Slide 89 text

std::sync::Arc Ist Send und Sync.

Slide 90

Slide 90 text

std::sync::Mutex Ein Container, der mutablen Zugriff auf die internen Daten erlaubt und zur Laufzeit prüft, dass er exklusiv ist.

Slide 91

Slide 91 text

std::sync::Mutex Ist Send und Sync.

Slide 92

Slide 92 text

Send und Sync abstrahieren über Daten. Was fehlt?

Slide 93

Slide 93 text

Semantisches Problem Rust ist synchron und imperativ: Aktionen finden nacheinander statt und können nicht pausiert werden.

Slide 94

Slide 94 text

Nebenläufige Programme sind: • Unvorhersagbar • Reaktiv • Zeitorientiert

Slide 95

Slide 95 text

Result enum Result { Ok(T), Err(E) }

Slide 96

Slide 96 text

Futures trait Future { type Item; type Error; fn poll(&mut self) -> Result, E>; fn wait(self) -> Result; }

Slide 97

Slide 97 text

Async • Ok(Async::Ready(t)) -> Die Future ist fertig • Ok(Async::NotReady) -> Die Future arbeitet noch • Err(e) -> Ein Fehler ist aufgetreten

Slide 98

Slide 98 text

Futures Futures abstrahieren eine Berechnung, die in der Zukunft beendet sein könnte und entweder erfolgreich oder zu einem Fehler führen wird.

Slide 99

Slide 99 text

Sozusagen: ein Result in der Zukunft.

Slide 100

Slide 100 text

Futures gibt es auch unter dem Namen "Promises".

Slide 101

Slide 101 text

Lingo • Eine Future ist "aufgelöst", wenn das Ergebnis da ist. • "deferred computation" • Futures am Ende einer Berechnung heissen "Blätter"

Slide 102

Slide 102 text

fn main() { let timer = Timer::default(); let sleep = timer.sleep(Duration::from_millis(1000)) .inspect("sleep"); let cpu_pool = CpuPool::new(4); let task = cpu_pool.spawn(sleep); println!("{:?}", task.wait()); }

Slide 103

Slide 103 text

Future sleep polled: Ok(NotReady) Future sleep polled: Ok(Ready(())) Ok(())

Slide 104

Slide 104 text

Exekutoren Exekutoren sind die Implementierung der Ausführungsstrategie von Futures.

Slide 105

Slide 105 text

futures-cpupool Mehrere Threads führen Futures parallel aus. Die Ausführung findet stets parallel statt.

Slide 106

Slide 106 text

tokio-core tokio-core hält eine Liste ausführbarer Futures und arbeitet diese eine nach dem anderen ab. Futures, die gerade nicht ausführbar sind, werden zurück in die Liste gebracht.

Slide 107

Slide 107 text

use tokio_core::reactor::Core; fn main() { let timer = Timer::default(); let sleep = timer.sleep(Duration::from_millis(1000)) .inspect("sleep"); let mut core = Core::new().unwrap(); let task = core.run(sleep); println!("{:?}", task); }

Slide 108

Slide 108 text

Future sleep polled: Ok(NotReady) Future sleep polled: Ok(Ready(())) Ok(())

Slide 109

Slide 109 text

Protokoll • Futures werden beim ersten poll() gestartet • Sie können beliebig oft "NotReady" antworten • Ein weiterer Aufruf von poll() nach "Ready" ist nicht erlaubt

Slide 110

Slide 110 text

Kombination von Futures fn main() { let timer = Timer::default(); let sleep = timer.sleep(Duration::from_millis(1000)) .inspect("sleep") .and_then(|_| { timer.sleep(Duration::from_millis(500)) .inspect("sleep some more") }); let mut core = Core::new().unwrap(); let task = core.run(sleep); println!("{:?}", task); }

Slide 111

Slide 111 text

Future sleep polled: Ok(NotReady) Future sleep polled: Ok(Ready(())) Future sleep some more polled: Ok(NotReady) Future sleep some more polled: Ok(Ready(())) Ok(())

Slide 112

Slide 112 text

Futures beschreiben Abhängigkeiten zwischen zukünftigen Berechnungen.

Slide 113

Slide 113 text

Result "Wenn jetzt X eintrat, dann..."

Slide 114

Slide 114 text

Future "Wenn in Zukunft X eintritt, dann..."

Slide 115

Slide 115 text

Kombinatoren: join_all join_all fügt mehrere Futures zu einer neuen Future zusammen, die aufgelöst wird, wenn alle Blätter aufgelöst werden.

Slide 116

Slide 116 text

fn main() { let timer = Timer::default(); let sleep = timer.sleep(Duration::from_millis(1500)) .inspect("sleep"); let sleep_shorter = timer.sleep(Duration::from_millis(500)) .inspect("short sleep"); let join = join_all(vec![sleep, sleep_shorter]) .inspect("join"); let mut core = Core::new().unwrap(); let result = core.run(join); println!("{:?}", result); }

Slide 117

Slide 117 text

Future sleep polled: Ok(NotReady) Future short sleep polled: Ok(NotReady) Future join polled: Ok(NotReady) Future sleep polled: Ok(NotReady) Future short sleep polled: Ok(Ready(())) Future join polled: Ok(NotReady) Future sleep polled: Ok(Ready(())) Future join polled: Ok(Ready([(), ()])) Ok([(), ()])

Slide 118

Slide 118 text

Kombinatoren: select_all select_all fügt mehrere Futures zu einer neuen Future zusammen, die aufgelöst wird, wenn das erste Kind aufgelöst werden.

Slide 119

Slide 119 text

fn main() { let timer = Timer::default(); let sleep = timer.sleep(Duration::from_millis(1000)) .inspect("sleep"); let short_sleep = timer.sleep(Duration::from_millis(500)) .inspect("short sleep");

Slide 120

Slide 120 text

let select = select_all(vec![sleep, short_sleep]) .inspect("first select"); let mut core = Core::new().unwrap(); let (result, index, remaining_futures) = core.run(select).un println!("Future with index {} returned {:?}", index, result); let select = select_all(remaining_futures) .inspect("second select"); let (result, index, _) = core.run(select).unwrap(); println!("Future with index {} returned {:?}", index, result); }

Slide 121

Slide 121 text

Future sleep polled: Ok(NotReady) Future short sleep polled: Ok(NotReady) Future first select polled: Ok(NotReady) Future sleep polled: Ok(NotReady) Future short sleep polled: Ok(Ready(())) Future first select polled: Ok(Ready(((), 1, [ InspectFuture { future: Sleep { timer: Timer, when: Instant Future with index 1 returned () Future sleep polled: Ok(NotReady) Future second select polled: Ok(NotReady) Future sleep polled: Ok(Ready(())) Future second select polled: Ok(Ready(((), 0, []))) Future with index 0 returned ()

Slide 122

Slide 122 text

Eine Umformung let select = select_all(vec![sleep, short_sleep]) .inspect("select_all") .and_then(|(result, index, futures)| { println!("Future with index {} returned {:?}", index, result); select_all(futures) .inspect("nested select_all") }); let mut core = Core::new().unwrap(); let (result, index, _) = core.run(select).unwrap(); println!("Future with index {} returned {:?}", index, result);

Slide 123

Slide 123 text

Future sleep polled: Ok(NotReady) Future short sleep polled: Ok(NotReady) Future select_all polled: Ok(NotReady) Future sleep polled: Ok(NotReady) Future short sleep polled: Ok(Ready(())) Future select_all polled: Ok(Ready(((), 1, [ InspectFuture { future: Sleep { timer: Timer, when: Instant Future with index 1 returned () Future sleep polled: Ok(NotReady) Future nested select_all polled: Ok(NotReady) Future sleep polled: Ok(Ready(())) Future nested select_all polled: Ok(Ready(((), 0, []))) Future with index 0 returned ()

Slide 124

Slide 124 text

Jetzt! Futures können auch sofortige Berechnung darstellen. Hierzu können wir von Results zu einer Future übergehen.

Slide 125

Slide 125 text

let file = File::open("absent"); let future = futures::result(file);

Slide 126

Slide 126 text

Wird diese Future gepollt, ist sie sofort fertig.

Slide 127

Slide 127 text

Futures sind ein kombinierbares Konzept zur Nebenläufigkeitsberechnung, das keine konkrete Ausführungstechnik vorgibt.

Slide 128

Slide 128 text

Futures sind momentan DAS beherrschende Konzept zur Nebenläufigkeitsabstraktion.

Slide 129

Slide 129 text

Beispiele • Java Futures • Promises/A in der JavaScript-Welt

Slide 130

Slide 130 text

Vorsicht Verwendung von Futures benötigt immernoch nachdenken über Send, Sync, Ownership und Borrowing.

Slide 131

Slide 131 text

Wie war das jetzt mit den Dateien?

Slide 132

Slide 132 text

Optional: ’static

Slide 133

Slide 133 text

Fazit Rust bietet ein zwar sehr komplexes, aber mächtiges Konzept zur Abstraktion über verschiedene Arten der Nebenläufigkeit.

Slide 134

Slide 134 text

Es bietet Lösungen für Sharing, Synchronisation und Ordnung.

Slide 135

Slide 135 text

Fehler in der Anwendung führen zu Compilerfehlern, nicht zu Laufzeitfehlern.

Slide 136

Slide 136 text

Concurrency without fear.