80
Spaß an der Nebenläufigkeit

Spaß an der Nebenläufigkeit

Embed Size (px)

Citation preview

Spaß an der Nebenläufigkeit

Frank Müller

Oldenburg / DeutschlandBaujahr 1965

Software-EntwicklerFachautor

[email protected]@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 bedeutetNebenläufigkeitin der Software?

Moderne Multicore-Systemesind 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önnenihre 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

JK

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

ServerA

ServerB'

ServerB

Startet undüberwacht

A und B

RegistriertAbsturz 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 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 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

Bset_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 123RFPexelsiStockphotoEigene Fotos