Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Was steckt hinter "Concurrency without Fear?"

Was steckt hinter "Concurrency without Fear?"

Florian Gilcher

January 16, 2018
Tweet

More Decks by Florian Gilcher

Other Decks in Programming

Transcript

  1. • Mitglied des globalen Rust commu- nity teams • Organisator

    eurucamp/jruby- conf.eu/RustFest/isleofruby
  2. • Erfunden von Graydon Hoare • Basiert auf linearen Typen

    • Regionen-basiertes Speicherman- agement
  3. • Entwickelt von Mozilla und der Community • Finanziert von

    Mozilla • Jetzt schon Firefox! (Firefox 56) • Insbesondere für parsing (URL und MP4-Metadata)
  4. Ziele von Rust • Speichersicherheit ohne Garbage Collection • Unterstützung

    bei Nebenläufigkeit • (Erwartbare) Geschwindigkeit
  5. Nebenläufigkeit • Mutabilität als Konzept erster Klasse • Keine konkurrierenden

    Zugriffe auf mutable Daten • Wichtige Basistypen mitgeliefert
  6. Das Ziel ist C • Kontrollierbares Memory-Layout • Kann C-ABI-kompatible

    Biblio- theken erstellen • Keine Laufzeit • optionale unsichere Subsprache (z.B. für FFI)
  7. Parallel 1 + 1 + 1 = 3, ausser in

    manchen seltenen Fällen
  8. Resource races • Mehrere Teile eines Programms entziehen sich gegenseitig

    Zugriff auf Resourcen, die sie aber alle benötigen
  9. fn main() { let mut v = vec![1; 10]; let

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

    element = &v[3]; v.push(1); println!("{}", element); }
  11. 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
  12. main = do let list = [1,2,3] print list let

    list2 = list ++ [4] print list print list2
  13. Beispiel: Erlang Erlang erlaubt weder die Mutation von Daten, noch

    deren Sharing. Werden Daten zu anderen Prozessteilen geschickt, werden sie immer kopiert.
  14. Beide Strategien können ohne Unterstützung der verwendeten Programmiersprache verwendet werden

    (und sind empfohlen), aber natürlich mit Sprachunterstützung viel leichter umzusetzen.
  15. import System.IO main = do inFile <- openFile "foo" ReadMode

    contents <- hGetLine inFile putStrLn contents hClose inFile contents <- hGetLine inFile putStrLn contents hClose inFile
  16. -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.
  17. 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)
  18. • Alle Daten haben genau einen Be- sitzer • Besitz

    kann abgegeben werden • Wenn Daten das Ende eines Scopes erreichen, werden sie zerstört
  19. 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!"); }
  20. • Zugriff kann verliehen werden (mu- tabel und immutabel) •

    Einmalig mutabel • Oder mehrfach immutabel • Exklusiv: mutable or immutabel, niemals beides
  21. 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!"); }
  22. • Ownership erlaubt uns share- nothing • Borrowing erlaubt uns

    sharing, aber ohne Mutation • Mutables borrowing garantiert exk- lusive Mutation
  23. Wir können also jederzeit von einem der Systeme ins andere

    wechseln, solange wir nur nach einem arbeiten.
  24. 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
  25. 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
  26. Nachteile • Jeder Thread braucht Platz für einen Stack •

    Es können recht einfach Synchro- nisierungsbugs auftreten
  27. • 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
  28. Vorteile • Eine Aktion zu einer Zeit: weniger Synchronisierungsprobleme •

    Blockaden werden durch Einreihen ans Ende der Queue abgebildet • Blockaden sind sehr günstig
  29. Nachteile • Aktionen, die den ausführenden Thread doch blockieren, blockieren

    alles • Schwer zu folgen, da Reihenfolge selbst herzustellen ist
  30. Send Ein Wert kann zwischen Threads abgegeben werden. Der sendende

    Thread verliert den Zugriff. Der Zugriff bleibt exklusiv.
  31. Sync Ein Wert kann zwischen Threads geteilt werden. Der sendende

    Thread behält den Zugriff. Der Zugriff wird geteilt.
  32. Send und Sync sind elegant Sie sagen nichts spezifisches über

    die verwendete Technologie aus. Sie erlauben aber spezifisches Arbeiten mit einer Technologie.
  33. std::sync::Arc Ein Pointer, der mehrfachen, zur Laufzeit geprüften, immutablen Zugriff

    auf Daten erlaubt. Synchronisiert seinen internen Zähler.
  34. std::sync::Mutex Ein Container, der mutablen Zugriff auf die internen Daten

    erlaubt und zur Laufzeit prüft, dass er exklusiv ist.
  35. Futures trait Future { type Item; type Error; fn poll(&mut

    self) -> Result<Async<T>, E>; fn wait(self) -> Result<T, E>; }
  36. Async • Ok(Async::Ready(t)) -> Die Future ist fertig • Ok(Async::NotReady)

    -> Die Future arbeitet noch • Err(e) -> Ein Fehler ist aufgetreten
  37. Futures Futures abstrahieren eine Berechnung, die in der Zukunft beendet

    sein könnte und entweder erfolgreich oder zu einem Fehler führen wird.
  38. Lingo • Eine Future ist "aufgelöst", wenn das Ergebnis da

    ist. • "deferred computation" • Futures am Ende einer Berechnung heissen "Blätter"
  39. 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()); }
  40. 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.
  41. 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); }
  42. Protokoll • Futures werden beim ersten poll() gestartet • Sie

    können beliebig oft "NotReady" antworten • Ein weiterer Aufruf von poll() nach "Ready" ist nicht erlaubt
  43. 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); }
  44. 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(())
  45. 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.
  46. 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); }
  47. 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([(), ()])
  48. 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.
  49. 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");
  50. 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); }
  51. 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 ()
  52. 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);
  53. 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 ()
  54. Fazit Rust bietet ein zwar sehr komplexes, aber mächtiges Konzept

    zur Abstraktion über verschiedene Arten der Nebenläufigkeit.