Share
Embed
HTWG: Events, Send & Sync
Gastvorlesung an der HTWG Konstanz zu Rust Futures
Florian Gilcher
May 23, 2017
Transcript
70 slides
-
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 -
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 -
Nachteile r Jeder Thread braucht Platz für einen Stack r
Es können recht einfach Synchro- nisierungsbugs auftreten -
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 -
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 -
Nachteile r Aktionen, die den ausführenden Thread doch blockieren, blockieren
alles r Schwer zu folgen, da Reihenfolge selbst herzustellen ist -
Populäre Evented-Systeme r Java NIO ("new I/O") r V8/node.js r
nginx r BEAM (Erlang-VM) -
Populäre Systeme mit beidem r Datenbanken, z.B. Elasticsearch r Spiele
r Fast jede moderne Desktop-App -
Send Ein Wert kann zwischen Threads abgegeben werden. Der sendende
Thread verliert den Zugriff. Der Zugriff bleibt exklusiv. -
Sync Ein Wert kann zwischen Threads geteilt werden. Der sendende
Thread behält den Zugriff. Der Zugriff wird geteilt. -
Send und Sync sind elegant Sie sagen nichts spezifisches über
die verwendete Technologie aus. Sie erlauben aber spezifisches Arbeiten mit einer Technologie. -
Semantisches Problem Rust ist synchron und imperativ: Aktionen finden nacheinander
statt und können nicht pausiert werden. -
Futures trait Future { type Item; type Error; fn poll(&mut
self) -> Result<Async<T>, E>; fn wait(self) -> Result<T, E>; } -
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 -
Futures Futures abstrahieren eine Berechnung, die in der Zukunft beendet
sein könnte und entweder erfolgreich oder zu einem Fehler führen wird. -
Lingo r Eine Future ist "aufgelöst", wenn das Ergebnis da
ist. r "deferred computation" r Futures am Ende einer Berechnung heissen "Blätter" -
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()); } -
futures-cpupool Mehrere Threads führen Futures parallel aus. Die Ausführung findet
stets parallel statt. -
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. -
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); } -
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 -
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); } -
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(()) -
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. -
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); } -
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([(), ()]) -
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. -
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"); -
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); } -
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 () -
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); -
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 () -
Jetzt! Futures können auch sofortige Berechnung darstellen. Hierzu können wir
von Results zu einer Future übergehen. -
Fazit Futures sind ein kombinierbares Konzept zur Nebenläufigkeitsberechnung, das keine
konkrete Ausführungstechnik vorgibt. -
Tokio Futures werden selten so roh wie hier verwendet. Als
Beispiel kann das Tokio-Framework dienen. https://tokio.rs/ -
Tokio r Ein Event-Framework (Reactor) r Eine Sammlung von Komponenten
zur Server-Entwicklung -
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<Item = Self::Response, Error = Self::Error>; /// Process the request and return the response asynchronous fn call(&self, req: Self::Request) -> Self::Future; } -
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 -
impl Service for MailboxService<String> { type Request = redisish::Command; type
Response = ServerResponse; type Error = io::Error; type Future = FutureResult<Self::Response, Self::Error>; fn call(&self, command: Self::Request) -> Self::Future { /// .... } } -
Futures und das darauf aufbauende Tokio bieten eine mächtige Abstraktion
über asynchrone Systeme.