Talk zum Thema Nebenläufigkeit auf der OOP 2016 in München. Neben einer prinzipiellen Einführung und Motivation werden die Sprachen Erlang/OTP, Google Go und Pony vorgestellt. Weiter sind einige typische Designmuster sowie Fallstricke enthalten. Der Vortrag hatte die Dauer von 90 Minuten.
4. 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
15. 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
19. Motivation in der Struktur ist wichtiger
• Kapselung des Zustands im Prozess
• Kommunikation über Nachrichten
• Sequentielle Verarbeitung
• Atomare Zustandsänderungen
• OOP im eigentlichen Sinne
20. ❝ Parallelverarbeitung
Programmierung als gleichzeitige
Ausführung möglicherweise
zusammen hängender Berechnungen.
Nebenläufigkeit
Programmierung als Komposition
unabhängig ausgeführter Prozesse.
–Rob Pike
21. Lange bekannte Ideen
Actor Model
1973
Carl Hewitt, Peter Bishop und Richard Steiger
Communicating Sequential Processes
1978
Tony Hoare
22. 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
24. 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
26. 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
36. 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
37. Erlang/OTP
• Nebenläufigkeit durch Actors
• Funktional und dynamisch typisiert
• Garbage Collections
• Pattern Matching und Guards
• Kommunikation über Rechnergrenzen
40. 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
41. Erlang/OTP - Komponenten der OTP 1/5
• application
• Komponente / Service
• Kann eigenständig gestartet und
gestoppt werden
• Verwaltet Konfiguration für enthaltene
Module
42. Erlang/OTP - Komponenten der OTP 2/5
• supervisor
• Startet konfigurierte Prozesse
• Kann sie nach Abstürzen erneut
starten
• Verhalten konfigurierbar
• Hierarchien möglich
43. 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
44. 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
45. 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
46. 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
47. 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}.
48. Google Go
• 2007 durch Google entwickelt
• Rob Pike, Ken Thompson und Robert
Griesemer
• Zielsetzung ist die
Systemprogrammierung
• Native Binaries
49. Google Go
• Nebenläufigkeit durch Goroutinen und
Channels
• Channels synchron und mit Puffer
• Imperative, objektorientierte, und
funktionale Aspekte
• Garbage Collection
50. 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
}
51. 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
}
}
}
52. 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
53. 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 …:
…
}
}
}
54. 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()
55. 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
}
56. Pony
• 2015 durch Causality entwickelt
• Sylvan Clebsch, Sebastian Blessing,
Sophia Drossopoulou, Andrew Mc Neil
• Fokus auf Sicherheit, Geschwindigkeit
und Einfachheit
• LLVM als Laufzeitumgebung
57. Pony
• Typ- und Speichersicher
• Keine Laufzeitfehler, Exceptions werden
immer behandelt
• Sicherstellung keiner Data Races
• Keine Deadlocks
• Garbage Collection
58. 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
59. 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())
60. Pony – Ping 2/3
actor Pinger
var _count: U64
new create() =>
_count = 0
be ping(ponger: Ponger) =>
_count = _count + 1
ponger.pong(_count)
61. 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
64. 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
65. 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
66. Volle Message Queues bzw. Kanäle 4/5
Client Server
do_this
get_data
API
return data
set_data
work
Client Process
67. 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
69. 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
73. Nicht-atomare Veränderungen 2/3
• Auslöser sind zu granuläre Nachrichten
und nicht eingehaltene Protokolle
• Zusammenhängende Daten gleichzeitig
ändern beziehungsweise auslesen
75. Blockaden durch Cycles 1/3
Process
A
Process
B
Process
C
get_foo
get_bar
get_yadda
return
return
76. 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
77. 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
79. Belohnung
• Natürliche Strukturen in der Software
• Elastisches Verhalten
• Problemlose Skalierung mit der
Hardware
• Hohe Sicherheit im Laufzeitverhalten