Spaß an der Nebenläufigkeit
Frank Müller
Oldenburg / Deutschland

Baujahr 1965

Software-Entwickler

Fachautor

mue@tideland.biz

@themue
Unsere Welt ist nebenläufig
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
Pflanzenwelt
Tierwelt
Menschen im Alltag
Menschen im Hobby
Menschen bei der Arbeit
Menschen auf Konferenzen
Unser alltägliches Zusammenleben
Was bedeutet

Nebenläufigkeit

in der Software?
Moderne Multicore-Systeme

sind wie die

Vereinigung von Flüssen
Ihre Energie muss

effizient genutzt werden
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
Nebenläufige Prozesse können

ihre eigenen Wege

gehen ...
... oder auch gemeinsame
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
Actor Model
A C D
GFEB
E1 E2 E3
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
Communicating Sequential Processes
A
B
C
D
E
CF G G H I
J
K
K
L
M
N
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
Einsatzsszenarien
Prozesse bieten Services an
Client
Client
Client
Server
get_this
do_that
set_something
return this
Prozesse verwalten zentrale Ressourcen
Client
Client
Client
Mana-
ger
A B
Prozesse dienen der Lastverteilung
Client
Client
Worker
Worker
Worker
Master
Prozesse verarbeiten Events
Client
Handler
Handler
Handler
Event
Server
Prozesse überwachen einander
Super-

visor
Server

A
Server

B'
Server

B
Startet und

überwacht
A und B
Registriert

Absturz von B und

startet B' neu
Prozesse sammeln und verteilen
Sender
Sender
Sender
In Out
Receiver
Receiver
Receiver
Prozesse arbeiten vernetzt
A C D
GFEB
E1 E2 E3
Beispiele nebenläufiger
Programmiersprachen
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
Fallstricke
Volle Message Queues bzw. Kanäle 1/5
Sender
Sender
Sender
Receiver
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 Server
do_this
get_data
API
return 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_by
return
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 1/3
Client
A
Server
Client
B
set_street
set_city get_street
get_city
Nicht-atomare Veränderungen 2/3
• Auslöser sind zu granuläre Nachrichten
und nicht eingehaltene Protokolle

• Zusammenhängende Daten gleichzeitig
ändern beziehungsweise auslesen
Nicht-atomare Veränderungen 3/3
Client
A
Server
Client
B
set_address get_address
Blockaden durch Cycles 1/3
Process
A
Process
B
Process
C
get_foo
get_bar
get_yadda
return
return
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
Belohnung
• Natürliche Strukturen in der Software

• Elastisches Verhalten

• Problemlose Skalierung mit der
Hardware

• Hohe Sicherheit im Laufzeitverhalten
Bildquellen
123RF

Pexels

iStockphoto

Eigene Fotos

Spaß an der Nebenläufigkeit