Slide 1

Slide 1 text

Events, Send und Sync

Slide 2

Slide 2 text

Wie können wir unzusammenhängende Berechnungen effizient für den Computer abbilden?

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Option 3: Beides Viel Spass!

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Mehr Sicherheit? Können wir Abstraktionen finden, die uns hier hilft?

Slide 14

Slide 14 text

Unabhängig von beiden Ansätzen?

Slide 15

Slide 15 text

Send und Sync

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Send und Sync abstrahieren über Daten. Was fehlt?

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 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 29

Slide 29 text

Sozusagen: ein Result in der Zukunft.

Slide 30

Slide 30 text

Futures gibt es auch unter dem Namen "Promises".

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

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

Slide 33

Slide 33 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 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 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 38

Slide 38 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 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 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 42

Slide 42 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 43

Slide 43 text

Futures beschreiben Abhängigkeiten zwischen zukünftigen Berechnungen.

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 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 47

Slide 47 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 48

Slide 48 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 49

Slide 49 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 50

Slide 50 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 51

Slide 51 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 52

Slide 52 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 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 53

Slide 53 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 54

Slide 54 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 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 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

Wird diese Future gepollt, ist sie sofort fertig.

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Futures sind momentan DAS beherrschende Konzept zur Nebenläufigkeitsabstraktion.

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

Tokio Futures werden selten so roh wie hier verwendet. Als Beispiel kann das Tokio-Framework dienen. https://tokio.rs/

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

Tokio r Ein Event-Framework (Reactor) r Eine Sammlung von Komponenten zur Server-Entwicklung

Slide 64

Slide 64 text

Tokio stellt teilweise Synchronität wieder her.

Slide 65

Slide 65 text

Halb-synchron: Tokio-Service pub trait Service { /// Requests handled by the service. type Request; /// Responses given by the service. type Response; /// Errors produced by the service. type Error; /// The future response value. type Future: Future; /// Process the request and return the response asynchronous fn call(&self, req: Self::Request) -> Self::Future; }

Slide 66

Slide 66 text

r Nimmt synchron eine Anfrage an r Gibt einen Wert zurück, der eine zukünftige Antwort anzeigt r Das asynchrone Aufrufen des Ser- vices übernimmt Tokio

Slide 67

Slide 67 text

impl Service for MailboxService { type Request = redisish::Command; type Response = ServerResponse; type Error = io::Error; type Future = FutureResult; fn call(&self, command: Self::Request) -> Self::Future { /// .... } }

Slide 68

Slide 68 text

Tokio abstrahiert darüber über asynchronen Input/Output.

Slide 69

Slide 69 text

https://tokio.rs/docs/getting- started/simple-server/

Slide 70

Slide 70 text

Futures und das darauf aufbauende Tokio bieten eine mächtige Abstraktion über asynchrone Systeme.