Frank Müller
Oldenburg / DeutschlandBaujahr 1965
Software-EntwicklerFachautor
[email protected]@themue
Naturprinzip
• Individuen bevölkern diese Welt
• Sie agieren mal von einander unabhängig, mal abhängig, mal gemeinsam
• Kommunikation und Signale ermöglichen ihr Zusammenleben
Eine Motivation in der Hardware
• Rechnerarchitekturen verändern sich
• Wachstum über CPUs, Kerne und Hyperthreads
• Manuelle Nutzung über Threads komplex und fehlerträchtig
• Nebenläufig Laufzeitumgebungen ermöglichen eine feingranulare Nutzung
Verteilung der Rechenleistung
CoreCoreCore Core
Laufzeitumgebung
Process Process Process ProcessProcess
Process Process Process ProcessProcess
Motivation in der Struktur ist wichtiger
• Kapselung des Zustands im Prozess
• Kommunikation über Nachrichten
• Sequentielle Verarbeitung
• Atomare Zustandsänderungen
• OOP im eigentlichen Sinne
❝ Parallelverarbeitung Programmierung als gleichzeitige
Ausführung möglicherweise zusammen hängender Berechnungen.
Nebenläufigkeit Programmierung als Komposition
unabhängig ausgeführter Prozesse.–Rob Pike
Lange bekannte Ideen
Actor Model 1973
Carl Hewitt, Peter Bishop und Richard Steiger
Communicating Sequential Processes 1978
Tony Hoare
Actor Model
• Aktoren sind nebenläufige Prozesse mit einer Adresse oder Kennung
• Kommunikation untereinander erfolgt asynchron über einen Nachrichtenversand an die Adresse
• Nachrichten werden sequentiell verarbeitet
• Prozesszustände sind gekapselt
Communicating Sequential Processes
• Nebenläufige Prozesse kommunizieren über einen oder mehrere Kanäle
• Prozesse sind im Gegensatz zu den Kanälen anonym
• Daten werden erst versandt, wenn der Empfänger bereit ist
• Eingehende Daten werden sequentiell verarbeitet
Technologien benötigen ihre Zeit
• Objektorientierung war lange bekannt
• Erste Implementierungen in Simula 67 und Smalltalk-76
• Breite Nutzung erst ab den 90ern durch C++, Java und C#
• Nun gewinnen nebenläufige Sprachen an Bedeutung
Prozesse überwachen einander
Super-visor
ServerA
ServerB'
ServerB
Startet undüberwacht
A und B
RegistriertAbsturz von B und
startet B' neu
Erlang/OTP
• 1986 durch Ericsson entwickelt
• Joe Armstrong, Robert Virding und Mike Williams
• Fokus auf Verteilung, Fehlertoleranz, Hochverfügbarkeit, nahezu Echtzeit und Non-Stop-Betrieb
• Eigene virtuelle Maschine
Erlang/OTP
• Nebenläufigkeit durch Actors
• Funktional und dynamisch typisiert
• Garbage Collections
• Pattern Matching und Guards
• Kommunikation über Rechnergrenzen
Erlang/OTP – Ping 1/2
-module(ping).
-export([start/0, ping/1]).
start() -> spawn(?MODULE, loop, [1]).
ping(Pid) -> Pid ! {ping, self()}, receive
{pong, _Loop, Count} -> {ok, Count} after
5000 -> {error, no_pong} end.
Erlang/OTP – Ping 2/2
stop(Pid) -> Pid ! stop, ok.
loop(Count) -> receive
{ping, Sender} -> Sender ! {pong, pid(), Count}, loop(Count + 1);
stop -> done
end.
Erlang/OTP - Bedeutung der OTP
• Open Telecom Platform
• Ursprünglich Fokus auf Telekommunikationsanlagen
• 1998 das System AXD301 mit einer Verfügbarkeit von 99,9999999% angekündigt
Erlang/OTP - Komponenten der OTP 1/5
• application
• Komponente / Service
• Kann eigenständig gestartet und gestoppt werden
• Verwaltet Konfiguration für enthaltene Module
Erlang/OTP - Komponenten der OTP 2/5
• supervisor
• Startet konfigurierte Prozesse
• Kann sie nach Abstürzen erneut starten
• Verhalten konfigurierbar
• Hierarchien möglich
Erlang/OTP - Komponenten der OTP 3/5
• gen_server
• Registrierter oder anonymer Service
• Synchrone und asynchrone Requests
• Verhalten durch Behaviours bestimmt
• Unterstützt Code-Updates im laufenden Betrieb
Erlang/OTP - Komponenten der OTP 4/5
• gen_event
• Registrierte oder anonyme Ereignisverarbeitung
• Mehrere Behaviours gleichzeitig möglich
• Unterstützt Code-Updates im laufenden Betrieb
Erlang/OTP - Komponenten der OTP 5/5
• gen_fsm
• Registrierte oder anonyme Zustandsautomaten
• Behaviour-Funktionen repräsentieren Zustände
• Unterstützt Code-Updates im laufenden Betrieb
Erlang/OTP – Behaviors 1/2
• Generische Module wie gen_server, gen_event und geb_fsm bieten Laufzeitfunktionalität
• Behaviour-Module definieren Callbacks mit Geschäftslogik
Erlang/OTP – Behaviours 2/2
-module(my_calc). -behaviour(gen_server).
% API.
add(A, B) -> gen_server(?MODULE, {add, A, B}, 5000).
% Callbacks.
handle_call({add, A, B}, _From, State) -> {reply, {ok, A + B}, State};
handle_call({mul, A, B}, _From, State) -> {reply, {ok, A * B}, State}.
Google Go
• 2007 durch Google entwickelt
• Rob Pike, Ken Thompson und Robert Griesemer
• Zielsetzung ist die Systemprogrammierung
• Native Binaries
Google Go
• Nebenläufigkeit durch Goroutinen und Channels
• Channels synchron und mit Puffer
• Imperative, objektorientierte, und funktionale Aspekte
• Garbage Collection
Google Go – Ping 1/2
type pongChan chan int type pingChan chan pongChan
type Ping stuct { pingCh pingChan count int
}
func New() *Ping { p := &Ping{
pingCh: make(pingChan), count: 0
} go p.loop() return p
}
Google Go – Ping 2/2
func (p*Ping) Ping() int { pongCh := make(pongChan) p.pingCh <- pongCh return <-pongCh
}
func (p *Ping) loop() { for {
select { case pongCh := <-p.pingCh:
p.count++ pongCh <- count
} }
}
Google Go – Kontrolle von Goroutinen 1/4
• Keine Referenzen auf Goroutinen
• Daher kein Monitoring wie in Erlang
• Steuerung und Überwachung durch Open-Source-Bibliotheken
• github.com/tideland/golib/loop
Google Go – Kontrolle von Goroutinen 2/4
func (t *MyType) backendLoop(l loop.Loop) error { for { select { case <-l.ShallStop(): return nil case foo := <-t.fooChan: if err := t.processFoo(foo); err != nil { return err } case …: … } } }
Google Go – Kontrolle von Goroutinen 3/4
// Start der Goroutine in der Startfunktion. t.loop = loop.Go(t.backendLoop)
// Dito mit Recover-Funktion. t.loop = loop.GoRecoverable(t.backendLoop, t.recoverLoop)
// Schleife beenden. err := t.loop.Stop()
// Schleife mit einem Fehler hart beenden und auf Ende warten. t.loop.Kill(myError) err := t.loop.Wait()
// Aktuellen Status und eventuellen Fehler abfragen. status, err := t.loop.Error()
Google Go – Kontrolle von Goroutinen 4/4
// Deadlock im Aufruf vermeiden. func (t *MyType) Foo(foo *Foo) error { select { case t.fooChan <- foo: case <-t.loop.IsStopping(): return errors.New("not running anymore") } return nil }
Pony
• 2015 durch Causality entwickelt
• Sylvan Clebsch, Sebastian Blessing, Sophia Drossopoulou, Andrew Mc Neil
• Fokus auf Sicherheit, Geschwindigkeit und Einfachheit
• LLVM als Laufzeitumgebung
Pony
• Typ- und Speichersicher
• Keine Laufzeitfehler, Exceptions werden immer behandelt
• Sicherstellung keiner Data Races
• Keine Deadlocks
• Garbage Collection
Pony – Capabilities gegen Data Races
• Isolated (iso) – verändern, weiterreichen
• Value (val) – unveränderlich
• Reference (ref) – veränderbar, nicht teilen
• Box (box) – sicheres lesen
• Transition (trn) – schreiben und anderen Lesezugriff geben
• Tag (tag) – nur Identifikation
Pony – Ping 1/3
use "collections"
actor Ponger var _env: Env
new create(env: Env) =>
_env = env
be pong(count: U64) => _env.out.print(count.string())
Pony – Ping 2/3
actor Pinger var _count: U64
new create() => _count = 0
be ping(ponger: Ponger) => _count = _count + 1 ponger.pong(_count)
Pony – Ping 3/3
actor Main let _ponger : Ponger let _pinger : Pinger
new create(env : Env) => _ponger = Ponger(env) _pinger = Pinger
for i in Range[U64](0, 5) do
pinger.ping(_ponger) end
Volle Message Queues bzw. Kanäle 2/5
• Nicht wie bei OOP überschneidender Zugriff
• Serialisierung eingehender Nachrichten
• Synchrone Zugriffe werden blockiert, Queues laufen voll und blockieren ebenso
Volle Message Queues bzw. Kanäle 3/5
• Last möglichst beim Aufrufer belassen
• Zentraler Prozess zur Datenverwaltung
• Weitere Last und Daten auf Arbeitsprozesse verteilen
• ETS in Erlang und Strukturen mit RWMutex in Go können ebenfalls helfen
Volle Message Queues bzw. Kanäle 4/5
Client Serverdo_this
get_data
APIreturn data
set_data
work
Client Process
Volle Message Queues bzw. Kanäle 5/5
Client B
Client A
Worker 3
Worker 2
Worker 1
Server
do_this do_this
return result
Race Conditions 1/4
Client A
Client B
Server
get_amount
get_amount
set_amount
set_amount
return amount
return amount
Race Conditions 2/4
• Überlagerndes Lesen und Setzen
• Update durch Delta mit Rückgabe des neuen Wertes
• Alternativ Rückgabe des Wertes mit Handle für Aktualisierung
Race Conditions 3/4
Client A
Client B
Server
update_amount_by
update_amount_byreturn new amount
return new amount
Race Conditions 4/4
Client A
Client B
Server
get_amount
get_amount
set_amount_with_handle
return amount and handle
return error "no handle"
Nicht-atomare Veränderungen 2/3
• Auslöser sind zu granuläre Nachrichten und nicht eingehaltene Protokolle
• Zusammenhängende Daten gleichzeitig ändern beziehungsweise auslesen
Blockaden durch Cycles 2/3
• Auslöser sind synchrone Abfragen
• Timeouts zeigen Blockaden auf, vermeiden jedoch nicht den Fehler
• Design auf Basis asynchroner Kommunikation
• Zustandsänderungen in den Prozessen müssen dies berücksichtigen
Blockaden durch Cycles 3/3
Process A
Process B
Process C
get_foo 1
get_bar 1
get_yadda 1
set_bar 1
set_foo 1
set_yadda 1
Belohnung
• Natürliche Strukturen in der Software
• Elastisches Verhalten
• Problemlose Skalierung mit der Hardware
• Hohe Sicherheit im Laufzeitverhalten