From 0c6a8c5622d58d1137686d26ac5728d74c8b7559 Mon Sep 17 00:00:00 2001 From: Robert Jeutter Date: Fri, 9 Apr 2021 22:33:55 +0200 Subject: [PATCH] minor bugfixes --- Programmierparadigmen.pdf | Bin 4971775 -> 4964923 bytes Programmierparadigmen.tex | 5131 +++++++++++++++++++------------------ 2 files changed, 2567 insertions(+), 2564 deletions(-) diff --git a/Programmierparadigmen.pdf b/Programmierparadigmen.pdf index 02729b17ebeaa26c9bd0ceca1c506a257ebf425c..92f6ffe7eae0e6cf521e6c87db216c5f59de59c0 100644 GIT binary patch delta 113028 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

SO-TaA>jOw_;8z}WPpOeeG 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 V+yYlXL{Lx&1jfBR_WB9#{{fw(h!g+- delta 119822 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 diff --git a/Programmierparadigmen.tex b/Programmierparadigmen.tex index 18c7692..d4e69df 100644 --- a/Programmierparadigmen.tex +++ b/Programmierparadigmen.tex @@ -341,11 +341,11 @@ public static void main(String[] args) { \item Aber auch eigene; u.a. durch Reflections abrufbar \item Häufig genutzt, wenn zusätzlicher Code geladen wird (Java EE) \item Oder um Unit-Tests zu markieren... -\item Nachteile: -\begin{itemize*} - \item Geringe Geschwindigkeit weil Zugriff über Programmcode erfolgt - \item Kapselung kann umgangen werden -\end{itemize*} + \item Nachteile: + \begin{itemize*} + \item Geringe Geschwindigkeit weil Zugriff über Programmcode erfolgt + \item Kapselung kann umgangen werden + \end{itemize*} \end{itemize*} \begin{lstlisting}[language=java] @@ -1919,8 +1919,10 @@ Variablen: \end{itemize*} Komplexe Datenstrukturen: +\begin{lstlisting} +[{{person,'Joe', 'Armstrong'}, {telephoneNumber, [3,5,9,7]}, {shoeSize, 42}, {pets, [{cat, tubby},{cat, tiger}]}, {children,[{thomas, 5},{claire,1}]}}, {{person,'Mike','Williams'}, {shoeSize,41}, {likes,[boats, beer]}, ...}] +\end{lstlisting} \begin{itemize*} - \item $[\{\{person,'Joe', 'Armstrong'\}, \{telephoneNumber, [3,5,9,7]\}, \{shoeSize, 42\}, \{pets, [\{cat, tubby\},\{cat, tiger\}]\}, \{children,[\{thomas, 5\},\{claire,1\}]\}\}, \{\{person,'Mike','Williams'\}, \{shoeSize,41\}, \{likes,[boats, beer]\}, ... \}]$ \item Erläuterungen: \begin{itemize*} \item Beliebig komplexe Strukturen können erzeugt werden @@ -2253,13 +2255,14 @@ Ein Lambda-Term ohne freie Variablen heißt Kombinator \subitem\colorbox{lightgray}{ \begin{minipage}[h]{0.9\linewidth} - $\lambda$-Terme\\ \\ + $\lambda$-Terme\\ \begin{tabular}[h]{lcr} \textbf {Bezeichnung} & \textbf{Notation} & \textbf{Beispiele} \\ Variablen & $x$ & $x \enspace y$ \\ Abstraktion & $\lambda$x.t & $\lambda y.0 \enspace \lambda f. \lambda x. \lambda y.fyx$ \\ Funktionsanwendung & $t_1t_2$ & $f 42 \enspace ( \lambda x.x+5)7$ \end{tabular} + (weitere primitive Operationen nach Bedarf) 17, True, +, · \end{minipage} } @@ -2328,14 +2331,14 @@ $$(\lambda x.\lambda y.\lambda z.f( \lambda x.z+x)(y x)) (\lambda y.y+x)$$ \end{itemize*} \end{itemize*} -\subsubsection{Ausführung von $\lambda$ Termen} -\colorbox{lightgray}{ \begin{minipage}[h]{1.0\linewidth} - \begin{tabular}{|p{4cm}|p{7cm}|} - Redex & Ein $\lambda$-Term der Form ($\lambda x.t_1)t_2$ heißt Redex. \\[\normalbaselineskip] - $\beta$-Reduktion & entspricht der Ausführung der Funktionanwendung auf einem Redex: \\[\normalbaselineskip] - & ($\lambda x.t_1)t_2 \Rightarrow t_1[x \rightarrow t_2]$ \\[\normalbaselineskip] - Substitution & $t_1[x \rightarrow t_2]$ erhält man aus dem Term $t_1$, wenn man alle freien Vorkommen von x durch $t_2$ ersetzt. \\[\normalbaselineskip] - Normalform & Ein Term, der nicht weiter reduziert werden kann, heißt in Normalform +\subsubsection{Ausführung von Lambda-Termen} +\colorbox{lightgray}{ \begin{minipage}[h]{.9\linewidth} + \begin{tabular}{ l | l } + Redex & Ein $\lambda$-Term der Form ($\lambda x.t_1)t_2$ heißt Redex. \\ + $\beta$-Reduktion & entspricht der Ausführung der Funktionanwendung auf einem Redex: \\ + & ($\lambda x.t_1)t_2 \Rightarrow t_1[x \rightarrow t_2]$ \\ + Substitution & $t_1[x \rightarrow t_2]$ erhält man aus dem Term $t_1$, wenn man alle freien Vorkommen von x durch $t_2$ ersetzt. \\ + Normalform & Ein Term, der nicht weiter reduziert werden kann, heißt in Normalform \end{tabular} \end{minipage} } @@ -2354,113 +2357,113 @@ Beispiel: let \\ Beispiel: let x = g y in f x berechnet f(g y)\\ \hspace*{14.5mm} ($\lambda x.fx)(g y) \Rightarrow f(g y)$ -\subsection{Äquivalenz} -\paragraph{$\alpha$-Äquivalenz} + \subsection{Äquivalenz} + \paragraph{$\alpha$-Äquivalenz} + + Namen gebundener Variablen + \begin{itemize*} + \item dienen letztlich nur der Dokumentation + \item entscheidend sind die Bindungen + \end{itemize*} + + \colorbox{lightgray}{ + \begin{minipage}[h]{.9\linewidth} + $\alpha$-Äquivalenz \\ + $t_1$ und $t_2$ heißen $\alpha$-Äquivalent ($t_1 \stackrel{\alpha}{=} t_2$), wenn $t_1$ in $t_2$ durch konsistente Umbenennung der $\lambda$-gebundenen Variablen überführt werden kann. + \end{minipage} + } + + Beispiele: + \begin{center} + $$\lambda x.x \stackrel{\alpha}{=} \lambda y.y$$ + $$\lambda x.\lambda z.f(\lambda y.zy)x \stackrel{\alpha}{=} \lambda y.\lambda x.f(\lambda z.xz)y$$ + \end{center} + aber + \begin{center} + $$\lambda x.\lambda z.f(\lambda y.zy)x \stackrel{\alpha}{\neq} \lambda x.\lambda z.g(\lambda y.zy)x$$ + $$\lambda z.\lambda z.f(\lambda y.zy)z\stackrel{\alpha}{\neq} \lambda x.\lambda z.f(\lambda y.zy)x$$ + \end{center} + + \paragraph{$\eta$-Äquivalenz} -Namen gebundener Variablen -\begin{itemize*} - \item dienen letztlich nur der Dokumentation - \item entscheidend sind die Bindungen -\end{itemize*} - -\colorbox{lightgray}{ - \begin{minipage}[h]{1.0\linewidth} - $\alpha$-Äquivalenz \\ - $ t_1$ und $t_2$ heißen $\alpha$-Äquivalent ($t_1 \stackrel{\alpha}{=} t_2$), wenn $t_1$ in $t_2$ durch konsistente Umbenennung der $\lambda$-gebundenen Variablen überführt werden kann. - \end{minipage} -} - -Beispiele: -\begin{center} - $$\lambda x.x \stackrel{\alpha}{=} \lambda y.y$$ - $$\lambda x.\lambda z.f(\lambda y.zy)x \stackrel{\alpha}{=} \lambda y.\lambda x.f(\lambda z.xz)y$$ -\end{center} -aber -\begin{center} - $$\lambda x.\lambda z.f(\lambda y.zy)x \stackrel{\alpha}{\neq} \lambda x.\lambda z.g(\lambda y.zy)x$$ - $$\lambda z.\lambda z.f(\lambda y.zy)z\stackrel{\alpha}{\neq} \lambda x.\lambda z.f(\lambda y.zy)x$$ -\end{center} - -\paragraph{$\eta$-Äquivalenz} - -Extensionalitäts-Prinzip: -\begin{itemize*} - \item Zwei Funktionen sind gleich, falls Ergebnis gleich für alle Argumente -\end{itemize*} -\colorbox{lightgray} { - \begin{minipage}[h]{1.0\linewidth} - $\eta$-Äquivalenz \\ - Terme $\lambda$x.fx und f heißen $\eta$-äquivalent ($\lambda$x.fx $\stackrel{\eta}{=}$ f), falls x nicht freie Variable von f ist. - \end{minipage} -} - -Beispiele: -$$\lambda x.\lambda y.f z x y \stackrel{\eta}{=}\lambda x.f z x$$ -$$f z \stackrel{\eta}{=}\lambda x.f z x$$ -$$\lambda x.x \stackrel{\eta}{=}\lambda x.(\lambda x.x)x$$ -aber $$\lambda x.f x x \stackrel{\eta}{\neq} f x$$ - -\subsection{Kodierung boolscher Werte} -Church Booleans -\begin{itemize*} - \item True wird zu: $C_{true} = \lambda t.\lambda f.t$ - \item False wird zu: $C_{false} = \lambda t.\lambda f.f$ - \item If-then-else wird zu: $If = \lambda a.a$ -\end{itemize*} - -Beispiel -\begin{lstlisting} + Extensionalitäts-Prinzip: + \begin{itemize*} + \item Zwei Funktionen sind gleich, falls Ergebnis gleich für alle Argumente + \end{itemize*} + \colorbox{lightgray} { + \begin{minipage}[h]{.9\linewidth} + $\eta$-Äquivalenz \\ + Terme $\lambda x.fx$ und f heißen $\eta$-äquivalent ($\lambda x.fx \stackrel{\eta}{=} f$), falls x nicht freie Variable von f ist. + \end{minipage} + } + + Beispiele: + $$\lambda x.\lambda y.f z x y \stackrel{\eta}{=}\lambda x.f z x$$ + $$f z \stackrel{\eta}{=}\lambda x.f z x$$ + $$\lambda x.x \stackrel{\eta}{=}\lambda x.(\lambda x.x)x$$ + aber $$\lambda x.f x x \stackrel{\eta}{\neq} f x$$ + + \subsection{Kodierung boolscher Werte} + Church Booleans + \begin{itemize*} + \item True wird zu: $C_{true} = \lambda t.\lambda f.t$ + \item False wird zu: $C_{false} = \lambda t.\lambda f.f$ + \item If-then-else wird zu: $If = \lambda a.a$ + \end{itemize*} + + Beispiel + \begin{lstlisting} if True then x else y \end{lstlisting} -ergibt: $(\lambda a.a)(\lambda t- \lambda f.t) x y = (\lambda t.\lambda f.t) xy = (\lambda f.x)y \Rightarrow x$ + ergibt: $(\lambda a.a)(\lambda t- \lambda f.t) x y = (\lambda t.\lambda f.t) xy = (\lambda f.x)y \Rightarrow x$ -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Lambda_Abstraktion.png} -\end{center} - -\begin{itemize*} - \item if True then x else y ergibt: - \subitem ($\lambda$\color{blue}a.a\color{black})(\color{red} $\lambda$t.$\lambda$f.t\color{black}) x y $\Rightarrow$ ($\lambda$\color{blue}t\color{black}.$\lambda$f.\color{blue}t\color{black}) \color{red}x\color{black} y $\Rightarrow$ ($\lambda$\color{blue}f\color{black}.x)\color{red}y\color{black} $\Rightarrow$ x - \item $b_1$ \&\& $b_2$ ist äquivalent zu if $b_1$ then $b_2$ else False - \subitem $\Rightarrow$ $b_1$ \&\& $b_2$ wird zu ($\lambda$a.a) $b_1$ $b_2$ $C_false$ - \subitem $\Rightarrow$ $b_1$ \&\& $b_2$ wird zu ($\lambda$a.a) $b_1$ $b_2$ ($\lambda$t.$\lambda$f.f) - \item True \&\& True ergibt: - \subitem \color{white} $\Rightarrow$ \color{black}($\lambda$\color{blue}a.a\color{black})\color{red}$C_true$ \color{black} $C_true$ ($\lambda$t.$\lambda$f.f) - \subitem $\Rightarrow$ ($\lambda$\color{blue}t\color{black}.$\lambda$f.\color{blue}t\color{black})\color{red}($\lambda$t.$\lambda$f.t)\color{black}($\lambda$t.$\lambda$f.f) - \subitem $\Rightarrow$ ($\lambda$\color{blue}f\color{black}.($\lambda$t.$\lambda$f.t)) \color{red}($\lambda$t.$\lambda$f.f)\color{black} $\Rightarrow$ $\lambda$t.$\lambda$f.f = $C_true$ - \item $b_1 \lor b_2$ entspricht: - \subitem if $b_1$ then True else $b_2$ - \item $\neg b_1$ entspricht: - \subitem if $b_1$ then False else True - \item $b_1 \Rightarrow b_2$ entspricht: - \subitem if $b_1$ then $b_2$ else True -\end{itemize*} - -\subsection{Kodierung natürlicher Zahlen} -Eine natürliche Zahl drückt aus, wie oft etwas geschehen soll. + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Lambda_Abstraktion.png} + \end{center} + + \begin{itemize*} + \item if True then x else y ergibt: + \subitem ($\lambda$\color{blue}a.a\color{black})(\color{red} $\lambda$t.$\lambda$f.t\color{black}) x y $\Rightarrow$ ($\lambda$\color{blue}t\color{black}.$\lambda$f.\color{blue}t\color{black}) \color{red}x\color{black} y $\Rightarrow$ ($\lambda$\color{blue}f\color{black}.x)\color{red}y\color{black} $\Rightarrow$ x + \item $b_1$ \&\& $b_2$ ist äquivalent zu if $b_1$ then $b_2$ else False + \subitem $\Rightarrow$ $b_1$ \&\& $b_2$ wird zu ($\lambda$a.a) $b_1$ $b_2$ $C_false$ + \subitem $\Rightarrow$ $b_1$ \&\& $b_2$ wird zu ($\lambda$a.a) $b_1$ $b_2$ ($\lambda$t.$\lambda$f.f) + \item True \&\& True ergibt: + \subitem \color{white} $\Rightarrow$ \color{black}($\lambda$\color{blue}a.a\color{black})\color{red}$C_true$ \color{black} $C_true$ ($\lambda$t.$\lambda$f.f) + \subitem $\Rightarrow$ ($\lambda$\color{blue}t\color{black}.$\lambda$f.\color{blue}t\color{black})\color{red}($\lambda$t.$\lambda$f.t)\color{black}($\lambda$t.$\lambda$f.f) + \subitem $\Rightarrow$ ($\lambda$\color{blue}f\color{black}.($\lambda$t.$\lambda$f.t)) \color{red}($\lambda$t.$\lambda$f.f)\color{black} $\Rightarrow$ $\lambda$t.$\lambda$f.f = $C_true$ + \item $b_1 \lor b_2$ entspricht: + \subitem if $b_1$ then True else $b_2$ + \item $\neg b_1$ entspricht: + \subitem if $b_1$ then False else True + \item $b_1 \Rightarrow b_2$ entspricht: + \subitem if $b_1$ then $b_2$ else True + \end{itemize*} + + \subsection{Kodierung natürlicher Zahlen} + Eine natürliche Zahl drückt aus, wie oft etwas geschehen soll. $c_0 = \lambda s.\lambda z.z$; $c_1=\lambda s.\lambda z.sz$; $c_2=\lambda s.\lambda z.s(sz)$;...;$c_n=\lambda s.\lambda z.s^n z$ -Arithmetische Operationen -\begin{itemize} - \item Addition: $plus = \lambda m. \lambda n. \lambda s. \lambda z. m s (n s z)$ - \item Multiplikation: $times = \lambda m. \lambda n. \lambda s. n (m s) = \lambda m. \lambda n. \lambda s. \lambda z. n (m s) z$ - \item Exponentiation: $exp = \lambda m. \lambda n. n m = \lambda m. \lambda n. \lambda s. \lambda z. n m s z$ - \item Vorgänger: $pred = \lambda n.\lambda s.\lambda x. n (\lambda y.\lambda z. z (y s))(K x)$ - \item Subtraktion: $sub = \lambda n.\lambda m. m pred n$ - \item Nullvergleich: $isZero = \lambda n. n (\lambda x. C false ) C true$ -\end{itemize} -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-kodierung-natürlicher-zahlen} -\end{center} + Arithmetische Operationen + \begin{itemize} + \item Addition: $plus = \lambda m. \lambda n. \lambda s. \lambda z. m s (n s z)$ + \item Multiplikation: $times = \lambda m. \lambda n. \lambda s. n (m s) = \lambda m. \lambda n. \lambda s. \lambda z. n (m s) z$ + \item Exponentiation: $exp = \lambda m. \lambda n. n m = \lambda m. \lambda n. \lambda s. \lambda z. n m s z$ + \item Vorgänger: $pred = \lambda n.\lambda s.\lambda x. n (\lambda y.\lambda z. z (y s))(K x)$ + \item Subtraktion: $sub = \lambda n.\lambda m. m pred n$ + \item Nullvergleich: $isZero = \lambda n. n (\lambda x. C false ) C true$ + \end{itemize} + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-kodierung-natürlicher-zahlen} + \end{center} + + succ($c_2$) = ($\lambda$\color{blue}n\color{black}.$\lambda s.\lambda z.s$ (\color{blue}n\color{black} s z)) \color{red} ($\lambda$s.$\lambda z.s (s z))$ \color{black} + \subitem $\Rightarrow\lambda s.\lambda z.s ((\lambda$\color{blue}s\color{black}.$\lambda z.$ \color{blue} s \color{black}(\color{blue}s\color{black} z)) s z) + \subitem $\Rightarrow\lambda s.\lambda z.s((\lambda$\color{blue}z\color{black}.s (s \color{blue}z\color{black}))\color{red}z\color{black}) + \subitem $\Rightarrow\lambda s.\lambda z.s(s(s z)) = c_3$ -succ($c_2$) = ($\lambda$\color{blue}n\color{black}.$\lambda s.\lambda z.s$ (\color{blue}n\color{black} s z)) \color{red} ($\lambda$s.$\lambda z.s (s z))$ \color{black} -\subitem $\Rightarrow\lambda s.\lambda z.s ((\lambda$\color{blue}s\color{black}.$\lambda z.$ \color{blue} s \color{black}(\color{blue}s\color{black} z)) s z) -\subitem $\Rightarrow\lambda s.\lambda z.s((\lambda$\color{blue}z\color{black}.s (s \color{blue}z\color{black}))\color{red}z\color{black}) -\subitem $\Rightarrow\lambda s.\lambda z.s(s(s z)) = c_3$ - -\subsection{Rechnen mit Church - Zahlen } -\includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-diamant-eigenschaft} -\begin{lstlisting}[language=erlang] + \subsection{Rechnen mit Church - Zahlen } + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-diamant-eigenschaft} + \begin{lstlisting}[language=erlang] Runnable task = () -> { String me = Thread.currentThread().getName(); System.out.println("Hallo " + me); @@ -2471,362 +2474,362 @@ task.run(); Thread thread = new Thread(task); thread.start(); \end{lstlisting} + + Idee zu exp: \\ + \subitem exp $c_m c_n \Rightarrow c_n c_m \Rightarrow (\lambda.\color{blue}s\color{black}.\lambda z.s^n z)\color{red}(\lambda s. \lambda z.s^m z)$ \color{black} + \subsubitem $\Rightarrow \lambda z.(\lambda s. \lambda z.s^m z)^n z$ + \subitem (per Induktion über n) $\stackrel{\alpha \beta \eta}{\Rightarrow}\lambda s.\lambda z. \lambda z.{s^m}^n z = {c_m}^n$ -Idee zu exp: \\ -\subitem exp $c_m c_n \Rightarrow c_n c_m \Rightarrow (\lambda.\color{blue}s\color{black}.\lambda z.s^n z)\color{red}(\lambda s. \lambda z.s^m z)$ \color{black} -\subsubitem $\Rightarrow \lambda z.(\lambda s. \lambda z.s^m z)^n z$ -\subitem (per Induktion über n) $\stackrel{\alpha \beta \eta}{\Rightarrow}\lambda s.\lambda z. \lambda z.{s^m}^n z = {c_m}^n$ - -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Arithmetische-Operationen} -\end{center} - -\subitem $isZero(c_0) = (\lambda\color{blue}n\color{black}.\color{blue}n\color{black}(\lambda x.C_{false})C_{true})\color{red} (\lambda s.\lambda z.z)\color{black}$ -\subsubitem $\Rightarrow (\lambda s. \lambda z. z)\color{red} (\lambda x.C_{false})\color{black} C_{true}$ -\subsubitem $\Rightarrow (\lambda \color{blue} z.z \color{black}) \color{red} C_{true} \Rightarrow C_{true}$ -(Bemerkung: I und K sind die Identitätsfunktion bzw. das Konstanten-Funktional) - -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Arithmetische-Operationen-2} -\end{center} - -\subsection{Auswertungsstrategien} -Wenn es in einem Term mehrere Redexe gibt, welchen reduziert man dann? -$$(\lambda x.x)((\lambda x.x)(\lambda z.(\lambda x.x)z))$$ -$$(\lambda \color{blue}x.x\color{black})\color{red}((\lambda x.x)(\lambda z.(\lambda x.x)z)) \color{black}$$ -$$(\lambda x.x)((\lambda \color{blue}x.x\color{black})\color{red}(\lambda z.(\lambda x.x)z)\color{black})$$ -$$(\lambda x.x)((\lambda x.x)(\lambda z.(\lambda \color{blue}x.x\color{black})\color{red}z\color{black})$$ -$$(\lambda x.x)((\lambda x.x)(\lambda z.(\lambda x.x)z))$$ - -Volle $\beta$-Reduktion: Jeder Redex kann jederzeit reduziert werden -$$(\lambda x.x)((\lambda x.x)(\lambda z.(\lambda \color{blue}x.x\color{black})\color{red}z\color{black}))$$ -$$\Rightarrow(\lambda x.x)((\lambda\color{blue}x.x\color{black})\color{red}(\lambda z.z)\color{black})$$ -$$\Rightarrow(\lambda \color{blue}x.x\color{black})\color{red}(\lambda z.z)\color{black})$$ -$$\Rightarrow(\lambda z.z)\color{black} \nRightarrow$$ -$$(\lambda x.x)((\lambda x.x)(\lambda z.(\lambda x.x)z))$$ - -Volle $\beta$-Reduktion: Jeder Redex kann jederzeit reduziert werden \\ -Normalreihenfolge: Immer der linkeste äußerste Redex wird reduziert -$$(\lambda x.x)((\lambda\color{blue}x.x\color{black})\color{red}(\lambda z.(\lambda x.x)z))\color{black}$$ -$$\Rightarrow(\lambda \color{blue}x.x\color{black})\color{red}(\lambda z.(\lambda x.x)z)\color{black}$$ -$$\Rightarrow\lambda z.(\lambda\color{blue}x.x\color{black})\color{red}z\color{black}$$ -$$\Rightarrow(\lambda z.z)\color{black}) \nRightarrow$$ - -\subsection{Fixpunktsatz und Rekursion} -\subsubsection{Divergenz} -Bisherige Beispiele werten zu einer Normalform aus. Aber: -$$\omega = (\lambda \color{blue}x.x x\color{black})\color{red}(\lambda x.x x) \color{black} \rightarrow (\lambda x.x x)(\lambda x.x x)$$ - + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Arithmetische-Operationen} + \end{center} + + \subitem $isZero(c_0) = (\lambda\color{blue}n\color{black}.\color{blue}n\color{black}(\lambda x.C_{false})C_{true})\color{red} (\lambda s.\lambda z.z)\color{black}$ + \subsubitem $\Rightarrow (\lambda s. \lambda z. z)\color{red} (\lambda x.C_{false})\color{black} C_{true}$ + \subsubitem $\Rightarrow (\lambda \color{blue} z.z \color{black}) \color{red} C_{true} \Rightarrow C_{true}$ + (Bemerkung: I und K sind die Identitätsfunktion bzw. das Konstanten-Funktional) + + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Arithmetische-Operationen-2} + \end{center} + + \subsection{Auswertungsstrategien} + Wenn es in einem Term mehrere Redexe gibt, welchen reduziert man dann? + $$(\lambda x.x)((\lambda x.x)(\lambda z.(\lambda x.x)z))$$ + $$(\lambda \color{blue}x.x\color{black})\color{red}((\lambda x.x)(\lambda z.(\lambda x.x)z)) \color{black}$$ + $$(\lambda x.x)((\lambda \color{blue}x.x\color{black})\color{red}(\lambda z.(\lambda x.x)z)\color{black})$$ + $$(\lambda x.x)((\lambda x.x)(\lambda z.(\lambda \color{blue}x.x\color{black})\color{red}z\color{black})$$ + $$(\lambda x.x)((\lambda x.x)(\lambda z.(\lambda x.x)z))$$ + + Volle $\beta$-Reduktion: Jeder Redex kann jederzeit reduziert werden + $$(\lambda x.x)((\lambda x.x)(\lambda z.(\lambda \color{blue}x.x\color{black})\color{red}z\color{black}))$$ + $$\Rightarrow(\lambda x.x)((\lambda\color{blue}x.x\color{black})\color{red}(\lambda z.z)\color{black})$$ + $$\Rightarrow(\lambda \color{blue}x.x\color{black})\color{red}(\lambda z.z)\color{black})$$ + $$\Rightarrow(\lambda z.z)\color{black} \nRightarrow$$ + $$(\lambda x.x)((\lambda x.x)(\lambda z.(\lambda x.x)z))$$ + + Volle $\beta$-Reduktion: Jeder Redex kann jederzeit reduziert werden \\ + Normalreihenfolge: Immer der linkeste äußerste Redex wird reduziert + $$(\lambda x.x)((\lambda\color{blue}x.x\color{black})\color{red}(\lambda z.(\lambda x.x)z))\color{black}$$ + $$\Rightarrow(\lambda \color{blue}x.x\color{black})\color{red}(\lambda z.(\lambda x.x)z)\color{black}$$ + $$\Rightarrow\lambda z.(\lambda\color{blue}x.x\color{black})\color{red}z\color{black}$$ + $$\Rightarrow(\lambda z.z)\color{black}) \nRightarrow$$ + + \subsection{Fixpunktsatz und Rekursion} + \subsubsection{Divergenz} + Bisherige Beispiele werten zu einer Normalform aus. Aber: + $$\omega = (\lambda \color{blue}x.x x\color{black})\color{red}(\lambda x.x x) \color{black} \rightarrow (\lambda x.x x)(\lambda x.x x)$$ + $\lambda x.xx$ wendet sein Argument auf das Argument selbst an $\Rightarrow$ dadurch reproduziert $\omega$ sich selbst. - -\colorbox{lightgray}{ - \begin{minipage}[h]{1.0\linewidth} - Divergenz: \\ - Terme, die nicht zu einer Normalform auswerten, divergieren. - Diese modellieren unendliche Ausführungen. - \end{minipage} -} - -\subsubsection{Der Fixpunktsatz} -\colorbox{lightgray}{ - \begin{minipage}[h]{1.0\linewidth} - Fixpunktsatz \\ - Für alle $F\in\Lambda$ existiert ein $X\in\lambda$ sodass gilt: $F X = X$ - \end{minipage} -} - -\begin{itemize*} - \item Der Fixpunktsatz besagt, dass im Lambda-Kalkül jeder Term einen Fixpunkt hat, d.h. einen Wert, der auf sich selber abgebildet wird. - \item Beweis: + + \colorbox{lightgray}{ + \begin{minipage}[h]{.9\linewidth} + Divergenz: \\ + Terme, die nicht zu einer Normalform auswerten, divergieren. + Diese modellieren unendliche Ausführungen. + \end{minipage} + } + + \subsubsection{Der Fixpunktsatz} + \colorbox{lightgray}{ + \begin{minipage}[h]{.9\linewidth} + Fixpunktsatz \\ + Für alle $F\in\Lambda$ existiert ein $X\in\lambda$ sodass gilt: $F X = X$ + \end{minipage} + } + \begin{itemize*} - \item Zu jedem beliebigen F sei $W =\lambda x.F(x x)$ und $X = (W W)$ - \item Dann gilt: $X\equiv WW \equiv(\lambda x.F(x x)) W \equiv F(W W) \equiv F X$ + \item Der Fixpunktsatz besagt, dass im Lambda-Kalkül jeder Term einen Fixpunkt hat, d.h. einen Wert, der auf sich selber abgebildet wird. + \item Beweis: + \begin{itemize*} + \item Zu jedem beliebigen F sei $W =\lambda x.F(x x)$ und $X = (W W)$ + \item Dann gilt: $X\equiv WW \equiv(\lambda x.F(x x)) W \equiv F(W W) \equiv F X$ + \end{itemize*} + \item Bemerkungen: + \begin{itemize*} + \item Für einige Lambda-Terme ist die Identifikation eines Fixpunktes einfach, z.B. für den Term $\lambda x.x$ (alle Terme sind Fixpunkte) + \item Für andere Terme, wie $\lambda xy.xy (= \lambda x.\lambda y.xy)$ ist das nicht so klar + \item Der Beweis des Fixpunktsatzes ist konstruiv, d.h. er liefert zu jedem Lambda-Term einen Fixpunkt + \end{itemize*} \end{itemize*} - \item Bemerkungen: + + \subsubsection{Anwendung des Fixpunktsatzes} \begin{itemize*} - \item Für einige Lambda-Terme ist die Identifikation eines Fixpunktes einfach, z.B. für den Term $\lambda x.x$ (alle Terme sind Fixpunkte) - \item Für andere Terme, wie $\lambda xy.xy (= \lambda x.\lambda y.xy)$ ist das nicht so klar - \item Der Beweis des Fixpunktsatzes ist konstruiv, d.h. er liefert zu jedem Lambda-Term einen Fixpunkt + \item Aufgabe: Berechne den Fixpunkt zum Term $\lambda xy.xy$ + \begin{itemize*} + \item Lösungsansatz: $W\equiv\lambda x.(\lambda\color{blue}x\color{black}y.\color{blue}x\color{black}y)\color{red}(xx) \color{black} \equiv\lambda x.\lambda y.(x x)y \equiv \lambda xy.(xx)y$ + \item Damit ist der gesuchte Fixpunkt $X\equiv((\lambda xy.(xx)y)(\lambda xy.(xx)y))$ + \item Nachrechnen: + \begin{description*} + \item[ \space ] $(\lambda xy.xy) ((\lambda xy.(xx)y) (\lambda xy.(xx)y))$ + \item[$\equiv$] $(\lambda \color{blue}x\color{black}.\lambda y. \color{blue} x \color{black} y) \color{red} ((\lambda xy.(xx)y) (\lambda xy.(xx)y)$ \color{black} + \item[$\equiv$] $\lambda \color{blue}y \color{black}.((\lambda xy.(xx)y)) (\lambda xy.(xx)y) \color{red}y\color{black}$ + \item[$\equiv$] $(\lambda xy.(xx)y)(\lambda xy.(xx)y)$ + \item[$\equiv$] X + \end{description*} + \end{itemize*} + \item Bemerkung: Der so für die Identitätsfunktion $\lambda x.x$ konstruierte Fixpunkt ist übrigens $(\lambda x.xx)(\lambda x.xx)$, er spielt die besondere Rolle des Standardterms $\bot$ für nicht-terminierende Ausführungen \end{itemize*} -\end{itemize*} - -\subsubsection{Anwendung des Fixpunktsatzes} -\begin{itemize*} - \item Aufgabe: Berechne den Fixpunkt zum Term $\lambda xy.xy$ + + \subsubsection{Der Fixpunkt-Kombinator} + \colorbox{lightgray}{ + \begin{minipage}[h]{.9\linewidth} + Im Ergebnis unserer Diskussion des Fixpunktsatzes definieren wir den Fixpunkt-Kombinator wie folgt: $$Y \equiv \lambda f.(\lambda x.f(xx)) (\lambda x.f(xx))$$ + \end{minipage} + } \begin{itemize*} - \item Lösungsansatz: $W\equiv\lambda x.(\lambda\color{blue}x\color{black}y.\color{blue}x\color{black}y)\color{red}(xx) \color{black} \equiv\lambda x.\lambda y.(x x)y \equiv \lambda xy.(xx)y$ - \item Damit ist der gesuchte Fixpunkt $X\equiv((\lambda xy.(xx)y)(\lambda xy.(xx)y))$ - \item Nachrechnen: - \begin{description*} - \item[ \space ] $(\lambda xy.xy) ((\lambda xy.(xx)y) (\lambda xy.(xx)y))$ - \item[$\equiv$] $(\lambda \color{blue}x\color{black}.\lambda y. \color{blue} x \color{black} y) \color{red} ((\lambda xy.(xx)y) (\lambda xy.(xx)y)$ \color{black} - \item[$\equiv$] $\lambda \color{blue}y \color{black}.((\lambda xy.(xx)y)) (\lambda xy.(xx)y) \color{red}y\color{black}$ - \item[$\equiv$] $(\lambda xy.(xx)y)(\lambda xy.(xx)y)$ - \item[$\equiv$] X - \end{description*} + \item Dieser Kombinator spielt eine wichtige Rolle bei der Definition rekursiver Funktionen im Lambda-Kalkül, wie wir im folgenden sehen werden + \item Für jeden Lambda-Term M gilt: $Y M = M (Y M)$ + \begin{itemize*} + \item Beweisidee: zeige, dass beide Terme auf einen identischen Term reduziert werden können + \end{itemize*} + \item Der Term Y ist übrigens nicht der einzige Kombinator, der Fixpunkte zu Lambda-Termen konstruiert + \begin{itemize*} + \item A. Turing: $\Theta \equiv (\lambda xy.y(xxy)) (\lambda xy.y(xxy))$ + \end{itemize*} \end{itemize*} - \item Bemerkung: Der so für die Identitätsfunktion $\lambda x.x$ konstruierte Fixpunkt ist übrigens $(\lambda x.xx)(\lambda x.xx)$, er spielt die besondere Rolle des Standardterms $\bot$ für nicht-terminierende Ausführungen -\end{itemize*} - -\subsubsection{Der Fixpunkt-Kombinator} -\colorbox{lightgray}{ - \begin{minipage}[h]{1.0\linewidth} - Im Ergebnis unserer Diskussion des Fixpunktsatzes definieren wir den Fixpunkt-Kombinator wie folgt: $$Y \equiv \lambda f.(\lambda x.f(xx)) (\lambda x.f(xx))$$ - \end{minipage} -} -\begin{itemize*} - \item Dieser Kombinator spielt eine wichtige Rolle bei der Definition rekursiver Funktionen im Lambda-Kalkül, wie wir im folgenden sehen werden - \item Für jeden Lambda-Term M gilt: $Y M = M (Y M)$ + + \subsubsection{Rekursion im Lambda-Kalkül} \begin{itemize*} - \item Beweisidee: zeige, dass beide Terme auf einen identischen Term reduziert werden können - \end{itemize*} - \item Der Term Y ist übrigens nicht der einzige Kombinator, der Fixpunkte zu Lambda-Termen konstruiert - \begin{itemize*} - \item A. Turing: $\Theta \equiv (\lambda xy.y(xxy)) (\lambda xy.y(xxy))$ - \end{itemize*} -\end{itemize*} - -\subsubsection{Rekursion im Lambda-Kalkül} -\begin{itemize*} - \item Die bisher definierten Funktionen waren alle nicht-rekursiv - \item Viele Funktionen kann man aber nur unter Zuhilfenahme von Rekursion (bzw. Iteration) beschreiben - \item In üblichen Programmiersprachen werden rekursive Funktionsdefinitionen durch die Verwendung von Namen für Funktionen möglich - man verwendet hierbei einfach den Namen der gerade zu definierenden Funktion im Rumpf der Definition: - \begin{lstlisting} + \item Die bisher definierten Funktionen waren alle nicht-rekursiv + \item Viele Funktionen kann man aber nur unter Zuhilfenahme von Rekursion (bzw. Iteration) beschreiben + \item In üblichen Programmiersprachen werden rekursive Funktionsdefinitionen durch die Verwendung von Namen für Funktionen möglich - man verwendet hierbei einfach den Namen der gerade zu definierenden Funktion im Rumpf der Definition: + \begin{lstlisting} \item fun fak(i) -> if (i = 0) then 1 else i * fak(i-1). \end{lstlisting} - \item Im Lambda-Kalkül gibt es jedoch keine Namen für Funktionen: - \begin{itemize*} - \item Daher stellt man eine rekursive Funktion f mittels einer Funktion G dar, die einen zusätzlichen Parameter g hat, an den man dann G selber bildet - \item Schaut kompliziert aus, ist es auch (Q-Q) - \item Warum so kompliziert? Damit die Definition von G im eigenen Rumpf verfügbar ist - \end{itemize*} -\end{itemize*} - -\subsubsection{Rekursive Funktionen sind Fixpunkte} -\begin{itemize*} - \item Rekursive Funktion von g - \begin{itemize*} - \item $g = \lambda n...g...n...$ Rumpf verwendet g - \end{itemize*} - \item Daraus gewinnt man das Funktional - \begin{itemize*} - \item $G = \lambda g.\lambda n...g...n...$ - \end{itemize*} - \item Falls G einen Fixpunkt g* hat, d.h. $G(g*) = g*$, so - \begin{itemize*} - \item $g* = G(g*) = \lambda n...g*...n$ - \end{itemize*} - \item Vergleiche: $g = \lambda n...g...n...$ -\end{itemize*} -\begin{center} - Rekursive Definition $\Leftrightarrow$ Fixpunkt des Funktionals -\end{center} -\begin{itemize*} - \item Beispiel: Fakultät - \begin{itemize*} - \item $g = \lambda n. $ if isZero n then $c_1$ else (times n g(pred n)) - rekursiv - \item $G = \lambda g.\lambda n.$ if isZero n then $c_1$ else (times n g(pred n)) - funktional - \end{itemize*} -\end{itemize*} - -\subsubsection{Der Fixpunktkombinator dient als Rekursionsoperator} -Wir berechnen den gesuchten Fixpunkt des Funktionals G mit dem Fixpunktkombinator, der somit als Rekusrsionsoperator dient: - -\color{blue} Rekursionsoperator \color{black} -$$Y = \lambda f.(\lambda x.f(xx))(\lambda x.f(xx))$$ -$$Y f = (\lambda \color{blue}f\color{black}.(\lambda x.\color{blue}f\color{black}(xx))(\lambda x.\color{blue}f\color{black}(xx))) \color{red}f\color{black}$$ -$$\Rightarrow (\lambda \color{blue}x\color{black}.f(\color{blue}xx\color{black})) \color{red}(\lambda x.f(xx))\color{black}$$ -$$\Rightarrow f((\lambda x.f(xx)) (\lambda x.f(xx)) \Leftarrow f(Yf) ())$$ -\color{black} -\subitem also \space\space\space \space $f(Yf) \stackrel{\beta}{=} Yf$ \\ -d.h. \space\space\space Yf ist Fixpunkt von f - -\paragraph{Beispiel: Fakultät im Lambda-Kalkül} -\includegraphics[width=.4\linewidth]{Assets/Programmierparadigmen-Lambda_Abstraktion} - -\subsection{Ausdrucksstärke des Lambdakalküls} -\begin{itemize*} - \item Im Folgenden wollen wir zeigen, dass der Lambda-Kalkül genau die rekursiven Funktionen beschreibt - \item Eine numerische Funktion ist eine Abbildung $f:\mathbb{N}^k \rightarrow \mathbb{N}$ mit $k\in\mathbb{N} \cap \{0\}$ - \item Wir definieren hierzu: - \begin{itemize*} - \item Anfangsfunktionen: + \item Im Lambda-Kalkül gibt es jedoch keine Namen für Funktionen: \begin{itemize*} - \item Projektion: $U_i^k (n_1,n_2,...,n_k) = n_i$ für $1<=i<=k$ - \item Nullfunktion: $Z(n) = 0$ - \item Nachfolger: $S(n) = n+1$ - \end{itemize*} - \item Minimalisierung: - \begin{itemize*} - \item Für eine Relation $P(m)$ bezeichne $\mu m[P(m)]$ die kleinste Zahl m sodass P(m) gilt. + \item Daher stellt man eine rekursive Funktion f mittels einer Funktion G dar, die einen zusätzlichen Parameter g hat, an den man dann G selber bildet + \item Schaut kompliziert aus, ist es auch (Q-Q) + \item Warum so kompliziert? Damit die Definition von G im eigenen Rumpf verfügbar ist \end{itemize*} \end{itemize*} - \item Bemerkung: im Folgenden notieren wir $n_1,n_2,...,n_k$ kurz als $\overline{n_k}$ - \item Eine numerische Funktion ist Lambda-definierbar, wenn es einen Kombinator M gibt, sodass $M\overline{n_k} = f(\overline{n_k})$ -\end{itemize*} - -\begin{itemize*} - \item Im folgenden sei C eine Klasse von numerischen Funktionen, und es gelte $g,h,h_1,h_2,...,h_m \in C$ - \item Wir definieren nun die folgenden Eigenschaften: + + \subsubsection{Rekursive Funktionen sind Fixpunkte} \begin{itemize*} - \item C ist \color{blue} abgeschlossen unter Komposition\color{black}, wenn für jede Funktion f, die über $f(\overline{n_k}):= g(h_1(\overline{n_k}),...,h_m(\overline{n_k}))$ definiert ist, gilt $f \in C$ - \item C ist \color{blue} abgeschlossen unter primitiver Rekursion\color{black}, wenn für jede Funktion f, die über - $$f(0,\overline{n_k}) = g(\overline{n_k})$$ - $$f(j+1, \overline{n_k}) = h(f(j,\overline{n_k}),j,\overline{n_k})$$ - definiert ist, gilt: $f \in C$ - \item C ist \color{blue} abgeschlossen unter unbeschränkter Minimalisierung \color{black}, wenn für jede Funktion f, die über $f(\overline{n_k})=\mu m[g(\overline{n_k},m)= 0]$ definiert ist (wobei für alle $\overline{n_k}$ ein m existiere, sodass $g(\overline{n_k},m) = 0$ ist), gilt $f \in C$ + \item Rekursive Funktion von g + \begin{itemize*} + \item $g = \lambda n...g...n...$ Rumpf verwendet g + \end{itemize*} + \item Daraus gewinnt man das Funktional + \begin{itemize*} + \item $G = \lambda g.\lambda n...g...n...$ + \end{itemize*} + \item Falls G einen Fixpunkt g* hat, d.h. $G(g*) = g*$, so + \begin{itemize*} + \item $g* = G(g*) = \lambda n...g*...n$ + \end{itemize*} + \item Vergleiche: $g = \lambda n...g...n...$ \end{itemize*} -\end{itemize*} - -\colorbox{lightgray}{\begin{minipage}[h]{1.0\linewidth} - Definition: \\ - Die Klasse der rekursiven Funktionen ist die kleinste Klasse numerischer Funktionen, die alle oben genannten Anfangsfunktionen enthält und abgeschlossen ist unter Komposition, primitiver Rekursion und unbeschränkter Minimalisierung - \end{minipage} -} - -\begin{itemize*} - \item \color{blue} Lemma 1: Die Anfangsfunktionen sind Lambda-definierbar \color{black} - \item Beweis: - \begin{itemize*} - \item $U_i^k = \lambda x_1 x_2 ... x_k.x_i$ - \item $S = \lambda n.\lambda s. \lambda z.s(nsz)$ (siehe succ bei Churchzahlen) - \item $Z = \lambda fx.x$ (siehe $c_0$ bei Churchzahlen) - \end{itemize*} -\end{itemize*} - -\begin{itemize*} - \item \color{blue} Lemma 2: Die Lambda-definierbaren Funktionen sind abgeschlossen unter primitiver Rekursion \color{black} - \item Beweis: Sei f definiert über \begin{center} - $$f(0,\overline{n_k}) = g(\overline{n_k})$$ - $$f(j+1, \overline{n_k}) = h(f(j, \overline{n_k}),j,\overline{n_k})$$ + Rekursive Definition $\Leftrightarrow$ Fixpunkt des Funktionals \end{center} - und seien g und h Funktionen (die per Induktionsvoraussetzung) durch die Lambda-terme G und H berechnet werden \begin{itemize*} - \item Intuitiv kann f berechnet werden, indem man überprüft ob j = 0 ist, und wenn ja $g(\overline{n_k})$, ansonsten $h(f(j, \overline{n_k}),j,\overline{n_k})$ - \item Ein Term M hierfür existiert laut Fixpunktsatz und es gilt: - $M \equiv Y (\lambda f\:x\: \overline{y_k}.if(isZero \: x)(G\:\overline{y_k})(H(f(pred\: x)\overline{y_k})(pred \: x)\overline{y_k}))$ + \item Beispiel: Fakultät + \begin{itemize*} + \item $g = \lambda n. $ if isZero n then $c_1$ else (times n g(pred n)) - rekursiv + \item $G = \lambda g.\lambda n.$ if isZero n then $c_1$ else (times n g(pred n)) - funktional + \end{itemize*} \end{itemize*} -\end{itemize*} - -\begin{itemize*} - \item \color{blue} Lemma 3: Die Lambda-definierbaren Funktionen sind abgeschlossen unter unbeschränkter Minimalisierung \color{black} - \item Beweis: - \begin{itemize*} - \item Sei f über $f(\overline{n_k}) = \mu m[g(\overline{n_k},m) = 0]$ definiert, wobei g (per Induktionsvoraussetzung) durch den Lambda-Term G berechnet wird - \item Intuitiv kann man f berechnen, indem man bei 0 beginnend für m überprüft, ob $g(\overline{n_k},m) = 0$ ist, und wenn ja m ausgibt, ansonsten die Überprüfung mit $m+1$ fortsetzt - \item Ein Term für eine solche Funktion kann laut Fixpunktsatz konstruiert werden und man erhält mit Anwendung des Fixpunktkombinators zunächst: $$N \equiv Y (\lambda f \: \overline{x_k} \: y. if(isZero \: (G \: \overline{x_k} \: y))y(f\:\overline{x_k}\:(succ \: y)))$$ - \item Nun definiert man die Funktion f durch den folgenden Term M: $$M \equiv \lambda \overline{x_k}.N \: \overline{x_k} \: c_0$$ - \end{itemize*} -\end{itemize*} - -\begin{center} - \includegraphics[width=.4\linewidth]{Assets/Programmierparadigmen-kodierung-natürlicher-zahlen} -\end{center} -\begin{itemize*} - \item Aus den Lemmata 1 bis 3 folgt nun der Satz:\\ - \colorbox{lightgray}{ - \begin{minipage}[h]{1.0\linewidth} - Alle rekursiven Funktionen sind Lambda-definierbar - \end{minipage}} -\end{itemize*} - - -\subsection{Berechnungsreihenfolgen und Konfluenz} -\subsubsection{Noch einmal Auswertungsstrategien} -\begin{itemize*} - \item Bei unserer initialen Betrachtung der Auswertungsstrategien haben wir die volle $\beta$-Rekursion und die Normalreihenfolge kennengelernt - \item Nun wollen wir unsere Betrachtungen hierzu noch einmal vertiefen und definieren zunächst: - \begin{itemize*} - \item Ein Redex wird als \color{blue} 'äußerst' (outermost) \color{black} bezeichnet, wenn er nicht Teil eines anderen Redex ist. - \item Ein Redex wird als \color{blue} 'innerst' (innermost) \color{black} bezeichnet, wenn er keinen eigenständigen Redex beinhaltet - \end{itemize*} - \item Mit diesen Begriffen können im folgenden die gebräuchlichsten Auswertungsstrategien formuliert werden - \begin{itemize*} - \item \color{blue} Normal Order: \color{black} Evaluiere Argumente so oft, wie sie verwendet werden - \item \color{blue} Applicative Order: \color{black} Evaluiere Argumente einmal - \item \color{blue} Lazy Evaluation: \color{black} Evaluiere Argumente höchstens einmal - \end{itemize*} - \item Eine zentrale Kernfrage: \color{blue} Welche Auswertungsstrategie führt (möglichst schnell) zu einem nicht weiter reduzierbaren Term? + + \subsubsection{Der Fixpunktkombinator dient als Rekursionsoperator} + Wir berechnen den gesuchten Fixpunkt des Funktionals G mit dem Fixpunktkombinator, der somit als Rekusrsionsoperator dient: + + \color{blue} Rekursionsoperator \color{black} + $$Y = \lambda f.(\lambda x.f(xx))(\lambda x.f(xx))$$ + $$Y f = (\lambda \color{blue}f\color{black}.(\lambda x.\color{blue}f\color{black}(xx))(\lambda x.\color{blue}f\color{black}(xx))) \color{red}f\color{black}$$ + $$\Rightarrow (\lambda \color{blue}x\color{black}.f(\color{blue}xx\color{black})) \color{red}(\lambda x.f(xx))\color{black}$$ + $$\Rightarrow f((\lambda x.f(xx)) (\lambda x.f(xx)) \Leftarrow f(Yf) ())$$ \color{black} + \subitem also \space\space\space \space $f(Yf) \stackrel{\beta}{=} Yf$ \\ + d.h. \space\space\space Yf ist Fixpunkt von f + + \paragraph{Beispiel: Fakultät im Lambda-Kalkül} + \includegraphics[width=.4\linewidth]{Assets/Programmierparadigmen-Lambda_Abstraktion} + + \subsection{Ausdrucksstärke des Lambdakalküls} \begin{itemize*} - \item Bei unserer beispielhaften Berechnung des Terms Fak $c_2$ haben wir nach der initialen Anwendung des Fixpunktkombinators zunächst den Term isZero $c_2$ reduziert. - \item Ebenso hätten wird den weiter innen stehenden Fixpunktkombinator zuerst erneut anwenden können(bei voller $\beta$-Reduktion kann jeder Term jederzeit reduziert werden). - \item Auf diese Weise hätten wir unendlich oft vorgehen, damit einen immer länger werdenden Term ableiten können und somit nicht das gewünschte Resultat $c_2$ berechnet. + \item Im Folgenden wollen wir zeigen, dass der Lambda-Kalkül genau die rekursiven Funktionen beschreibt + \item Eine numerische Funktion ist eine Abbildung $f:\mathbb{N}^k \rightarrow \mathbb{N}$ mit $k\in\mathbb{N} \cap \{0\}$ + \item Wir definieren hierzu: + \begin{itemize*} + \item Anfangsfunktionen: + \begin{itemize*} + \item Projektion: $U_i^k (n_1,n_2,...,n_k) = n_i$ für $1<=i<=k$ + \item Nullfunktion: $Z(n) = 0$ + \item Nachfolger: $S(n) = n+1$ + \end{itemize*} + \item Minimalisierung: + \begin{itemize*} + \item Für eine Relation $P(m)$ bezeichne $\mu m[P(m)]$ die kleinste Zahl m sodass P(m) gilt. + \end{itemize*} + \end{itemize*} + \item Bemerkung: im Folgenden notieren wir $n_1,n_2,...,n_k$ kurz als $\overline{n_k}$ + \item Eine numerische Funktion ist Lambda-definierbar, wenn es einen Kombinator M gibt, sodass $M\overline{n_k} = f(\overline{n_k})$ \end{itemize*} - \item Eine weitere Kernfrage: Angenommen mehrere unterschiedliche Reduktionsreihenfolgen führen zu einem nicht weiter zu reduzierenden Ergebnis - \color{blue} führen all diese Reihenfolgen zum gleichen Ergebnis? \color{black} - \item Wir definieren zuerst einen zentralen begriff in diesem Zusammenhang: - \colorbox{lightgray}{\begin{minipage}[h]{1.0\linewidth} - Ein Transitiosnsystem $(D,\rightarrow*)$ heißt genau dann konfluent, wenn für alle $t,t_1,t_2 \in D$ gilt: wenn $ t \rightarrow* t_1$ und $t \rightarrow* t_2$, dann gibt es ein $t' \in D$ mit $t_1 \rightarrow* t'$ und $t_2 \rightarrow* t'$ - \end{minipage}} - \item Wenn der Lambda-Kalkül konfluent ist, kann hieraus gefolgert werden, dass unterschiedliche Reduktionsreihenfolgen, die zu einer nicht mehr weiter zu reduzierenden Form führen, somit auf den gleichen Term führen müssen. - \item Achtung: hieraus kann nicht gefolgert werden, dass alle Reduktionsreihenfolgen auf den gleichen Term führen, da dies ja nur für 'terminierende' Reduktionsreihenfolgen gilt! -\end{itemize*} - -\subsubsection{Church-Rosser-Eigenschaft} -\color{blue} Satz (Church-Rosser) \\ -Der untypisierte $\lambda$-Kalkül ist konfluent: Wenn $t \stackrel{*}{\Rightarrow} t_1$ und $t \stackrel{*}{\Rightarrow} t_2$, dann gibt es ein t' mit $t_1 \stackrel{*}{\Rightarrow} t'$ und $t_2 \stackrel{*}{\Rightarrow} t'$ -\color{black} - -\begin{center} - \includegraphics[width=0.25\linewidth]{Assets/Programmierparadigmen-diamant-eigenschaft.png} - \includegraphics[width=0.25\linewidth]{Assets/Programmierparadigmen-diamant-beispiel} -\end{center} - -Beweisidee: Definiere $\stackrel{\rightarrow}{\rightarrow}$ als 'parallele' $\beta$-Reduktion. -\begin{itemize*} - \item Es gilt: $\Rightarrow \subseteq \stackrel{\rightarrow}{\rightarrow} \subseteq \stackrel{*}{\Rightarrow}$ - \item Zeige Diamant Eigenschaft für $\stackrel{\rightarrow}{\rightarrow}$ -\end{itemize*} - -\subsubsection{Eindeutigkeit der Normalform} -\color{blue} Korollar (Eindeutigkeit der Normalform) \newline -Die Normalform eines $\lambda$-Terms ist - sofern sie existiert - eindeutig. \color{black} -\newline -\newline -Beweis: -\begin{itemize*} - \item $t_1$ und $t_2$ Normalformen von t, d.h. $t \stackrel{*}{\Rightarrow} t_1 \nRightarrow$ und $t \stackrel{*}{\Rightarrow} t_2 \nRightarrow$ - \item Nach Chruch-Rosser gibt es t' mit $t_1 \stackrel{*}{\Rightarrow} t'$ und $t_2 \stackrel{*}{\Rightarrow} t'$ - \item Nach Annahme $t_1 \nRightarrow$ und $t_2 \nRightarrow$, also $t_1 = t' = t_2$ -\end{itemize*}\ \newline -\color{blue} -Bei terminierenden $\beta$-Reduktionen ist irrelevant, welchen Redex man zuerst reduziert!\color{black} -\subsection{Auswertung von Parametern in Programmiersprachen} -\subsubsection{Behandlung von Parametern in Programmiersprachen} -\begin{itemize*} - \item Die Art und Weise, wie in einer Programmiersprache Parametter übergeben - d.h. wie die Reihenfolge und die Zeitpunkte ihrer Auswertung gehandhabt - werden, hat Einfluss auf wichtige Eigenschaften der Sprache: + \begin{itemize*} - \item Effizienz der Berechnungen - \item Termininerungsverhalten - \item Ausdruckskraft + \item Im folgenden sei C eine Klasse von numerischen Funktionen, und es gelte $g,h,h_1,h_2,...,h_m \in C$ + \item Wir definieren nun die folgenden Eigenschaften: + \begin{itemize*} + \item C ist \color{blue} abgeschlossen unter Komposition\color{black}, wenn für jede Funktion f, die über $f(\overline{n_k}):= g(h_1(\overline{n_k}),...,h_m(\overline{n_k}))$ definiert ist, gilt $f \in C$ + \item C ist \color{blue} abgeschlossen unter primitiver Rekursion\color{black}, wenn für jede Funktion f, die über + $$f(0,\overline{n_k}) = g(\overline{n_k})$$ + $$f(j+1, \overline{n_k}) = h(f(j,\overline{n_k}),j,\overline{n_k})$$ + definiert ist, gilt: $f \in C$ + \item C ist \color{blue} abgeschlossen unter unbeschränkter Minimalisierung \color{black}, wenn für jede Funktion f, die über $f(\overline{n_k})=\mu m[g(\overline{n_k},m)= 0]$ definiert ist (wobei für alle $\overline{n_k}$ ein m existiere, sodass $g(\overline{n_k},m) = 0$ ist), gilt $f \in C$ + \end{itemize*} \end{itemize*} - \item Hierbei ist es insbesondere von Interesse, wie Parameter gehandhabt werden, deren Werte undefiniert sind (z.B. 1/0)\newline - \colorbox{lightgray}{ - \begin{minipage}[h]{1.0\linewidth} - Wir definieren zunächst den zentralen begriff 'strikt': \newline Eine n-stellige Funktion heißt strikt im k-ten Argument $(1<=k<=n)$, wenn gilt: $f(x_1,x_2,...,x_{k-1},\bot,x_{k+1},...,x_n)=\bot$ - \end{minipage}} - \item Ein undefiniertes Argument führt hier zu einem undefinierten Resultat - \item Grundsätzlich kann man die Auswertungsstrategien von Programmiersprachen in strikte und nicht-strikte Strategien einteilen; sehr gebräuchlich sind dabei insbesondere: + + \colorbox{lightgray}{\begin{minipage}[h]{.9\linewidth} + Definition: \\ + Die Klasse der rekursiven Funktionen ist die kleinste Klasse numerischer Funktionen, die alle oben genannten Anfangsfunktionen enthält und abgeschlossen ist unter Komposition, primitiver Rekursion und unbeschränkter Minimalisierung + \end{minipage} + } + \begin{itemize*} - \item Call by Value: Ausdrücke, die Parameter bei einem Funktionsaufruf beschreiben, werden vor der Übergabe an die Funktion vollständig ausgewertet - \item Call by Name: Ausdrücke, die Parameter bei einem Funktionsaufruf beschreiben, werden nicht bei Übergabe, sondern erst dann ausgewertet, wenn sie in der aufgerufenen Funktion tatsächlich benötigt werden + \item \color{blue} Lemma 1: Die Anfangsfunktionen sind Lambda-definierbar \color{black} + \item Beweis: + \begin{itemize*} + \item $U_i^k = \lambda x_1 x_2 ... x_k.x_i$ + \item $S = \lambda n.\lambda s. \lambda z.s(nsz)$ (siehe succ bei Churchzahlen) + \item $Z = \lambda fx.x$ (siehe $c_0$ bei Churchzahlen) + \end{itemize*} \end{itemize*} - \item Beide Varianten haben spezifische Vor- und Nachteile: + \begin{itemize*} - \item Call by Value: weniger Berechnungsaufwand, wenn ein Parameter mehr als einmal im Funktionsrumpf vorkommt; weniger Speicheraufwand bei der Übergabe - \item Call by Name: weniger Berechnungsaufwand, wenn ein Argument nicht zum Ergebnis beiträgt; höherer Aufwand bei Übergabe + \item \color{blue} Lemma 2: Die Lambda-definierbaren Funktionen sind abgeschlossen unter primitiver Rekursion \color{black} + \item Beweis: Sei f definiert über + \begin{center} + $$f(0,\overline{n_k}) = g(\overline{n_k})$$ + $$f(j+1, \overline{n_k}) = h(f(j, \overline{n_k}),j,\overline{n_k})$$ + \end{center} + und seien g und h Funktionen (die per Induktionsvoraussetzung) durch die Lambda-terme G und H berechnet werden + \begin{itemize*} + \item Intuitiv kann f berechnet werden, indem man überprüft ob j = 0 ist, und wenn ja $g(\overline{n_k})$, ansonsten $h(f(j, \overline{n_k}),j,\overline{n_k})$ + \item Ein Term M hierfür existiert laut Fixpunktsatz und es gilt: + $M \equiv Y (\lambda f\:x\: \overline{y_k}.if(isZero \: x)(G\:\overline{y_k})(H(f(pred\: x)\overline{y_k})(pred \: x)\overline{y_k}))$ + \end{itemize*} \end{itemize*} - \item Die Programmiersprache Erlang realisiert grundsätzlich eine strikte Handhabung von Parametern, da sie die Strategie Call by Value verwendet - \item Allerdings wird bei der Definition einer Funktion der resultierende Wert erst dann berechnet, wenn die Funktion ausgewertet wird + \begin{itemize*} - \item Das erlaubt über den Umweg zusätzlicher Funktionsdefinitionen auch die Realisierung einer nicht-strikten Auswertungsstrategie - ermöglicht Nachbildung der sogenannten Lazy-Evaluation - \item hierbei wird ein nicht-strikt zu evaluierendes Argument als Resultat einer anonymen nullstelligen Funktion (ohne Parameter) 'verpackt' - \item Im Rumpf der eigentlichen Funktion wird diese Funktion dann ausgewertet (= aufgerufen), wenn feststeht, dass dieses Argument für die Berechnung des Ergebnisses benötigt wird - \item Andere funktionale Sprachen wie Haskell oder Gofer verwenden Call by Name und realisieren damit grundsätzlich Lazy-Evaluation + \item \color{blue} Lemma 3: Die Lambda-definierbaren Funktionen sind abgeschlossen unter unbeschränkter Minimalisierung \color{black} + \item Beweis: + \begin{itemize*} + \item Sei f über $f(\overline{n_k}) = \mu m[g(\overline{n_k},m) = 0]$ definiert, wobei g (per Induktionsvoraussetzung) durch den Lambda-Term G berechnet wird + \item Intuitiv kann man f berechnen, indem man bei 0 beginnend für m überprüft, ob $g(\overline{n_k},m) = 0$ ist, und wenn ja m ausgibt, ansonsten die Überprüfung mit $m+1$ fortsetzt + \item Ein Term für eine solche Funktion kann laut Fixpunktsatz konstruiert werden und man erhält mit Anwendung des Fixpunktkombinators zunächst: $$N \equiv Y (\lambda f \: \overline{x_k} \: y. if(isZero \: (G \: \overline{x_k} \: y))y(f\:\overline{x_k}\:(succ \: y)))$$ + \item Nun definiert man die Funktion f durch den folgenden Term M: $$M \equiv \lambda \overline{x_k}.N \: \overline{x_k} \: c_0$$ + \end{itemize*} \end{itemize*} - - \begin{lstlisting}[language=erlang] + + \begin{center} + \includegraphics[width=.4\linewidth]{Assets/Programmierparadigmen-kodierung-natürlicher-zahlen} + \end{center} + \begin{itemize*} + \item Aus den Lemmata 1 bis 3 folgt nun der Satz:\\ + \colorbox{lightgray}{ + \begin{minipage}[h]{.9\linewidth} + Alle rekursiven Funktionen sind Lambda-definierbar + \end{minipage}} + \end{itemize*} + + + \subsection{Berechnungsreihenfolgen und Konfluenz} + \subsubsection{Noch einmal Auswertungsstrategien} + \begin{itemize*} + \item Bei unserer initialen Betrachtung der Auswertungsstrategien haben wir die volle $\beta$-Rekursion und die Normalreihenfolge kennengelernt + \item Nun wollen wir unsere Betrachtungen hierzu noch einmal vertiefen und definieren zunächst: + \begin{itemize*} + \item Ein Redex wird als \color{blue} 'äußerst' (outermost) \color{black} bezeichnet, wenn er nicht Teil eines anderen Redex ist. + \item Ein Redex wird als \color{blue} 'innerst' (innermost) \color{black} bezeichnet, wenn er keinen eigenständigen Redex beinhaltet + \end{itemize*} + \item Mit diesen Begriffen können im folgenden die gebräuchlichsten Auswertungsstrategien formuliert werden + \begin{itemize*} + \item \color{blue} Normal Order: \color{black} Evaluiere Argumente so oft, wie sie verwendet werden + \item \color{blue} Applicative Order: \color{black} Evaluiere Argumente einmal + \item \color{blue} Lazy Evaluation: \color{black} Evaluiere Argumente höchstens einmal + \end{itemize*} + \item Eine zentrale Kernfrage: \color{blue} Welche Auswertungsstrategie führt (möglichst schnell) zu einem nicht weiter reduzierbaren Term? + \color{black} + \begin{itemize*} + \item Bei unserer beispielhaften Berechnung des Terms Fak $c_2$ haben wir nach der initialen Anwendung des Fixpunktkombinators zunächst den Term isZero $c_2$ reduziert. + \item Ebenso hätten wird den weiter innen stehenden Fixpunktkombinator zuerst erneut anwenden können(bei voller $\beta$-Reduktion kann jeder Term jederzeit reduziert werden). + \item Auf diese Weise hätten wir unendlich oft vorgehen, damit einen immer länger werdenden Term ableiten können und somit nicht das gewünschte Resultat $c_2$ berechnet. + \end{itemize*} + \item Eine weitere Kernfrage: Angenommen mehrere unterschiedliche Reduktionsreihenfolgen führen zu einem nicht weiter zu reduzierenden Ergebnis - \color{blue} führen all diese Reihenfolgen zum gleichen Ergebnis? \color{black} + \item Wir definieren zuerst einen zentralen begriff in diesem Zusammenhang: + \colorbox{lightgray}{\begin{minipage}[h]{.9\linewidth} + Ein Transitiosnsystem $(D,\rightarrow*)$ heißt genau dann konfluent, wenn für alle $t,t_1,t_2 \in D$ gilt: wenn $ t \rightarrow* t_1$ und $t \rightarrow* t_2$, dann gibt es ein $t' \in D$ mit $t_1 \rightarrow* t'$ und $t_2 \rightarrow* t'$ + \end{minipage}} + \item Wenn der Lambda-Kalkül konfluent ist, kann hieraus gefolgert werden, dass unterschiedliche Reduktionsreihenfolgen, die zu einer nicht mehr weiter zu reduzierenden Form führen, somit auf den gleichen Term führen müssen. + \item Achtung: hieraus kann nicht gefolgert werden, dass alle Reduktionsreihenfolgen auf den gleichen Term führen, da dies ja nur für 'terminierende' Reduktionsreihenfolgen gilt! + \end{itemize*} + + \subsubsection{Church-Rosser-Eigenschaft} + \color{blue} Satz (Church-Rosser) \\ + Der untypisierte $\lambda$-Kalkül ist konfluent: Wenn $t \stackrel{*}{\Rightarrow} t_1$ und $t \stackrel{*}{\Rightarrow} t_2$, dann gibt es ein t' mit $t_1 \stackrel{*}{\Rightarrow} t'$ und $t_2 \stackrel{*}{\Rightarrow} t'$ + \color{black} + + \begin{center} + \includegraphics[width=0.25\linewidth]{Assets/Programmierparadigmen-diamant-eigenschaft.png} + \includegraphics[width=0.25\linewidth]{Assets/Programmierparadigmen-diamant-beispiel} + \end{center} + + Beweisidee: Definiere $\stackrel{\rightarrow}{\rightarrow}$ als 'parallele' $\beta$-Reduktion. + \begin{itemize*} + \item Es gilt: $\Rightarrow \subseteq \stackrel{\rightarrow}{\rightarrow} \subseteq \stackrel{*}{\Rightarrow}$ + \item Zeige Diamant Eigenschaft für $\stackrel{\rightarrow}{\rightarrow}$ + \end{itemize*} + + \subsubsection{Eindeutigkeit der Normalform} + \color{blue} Korollar (Eindeutigkeit der Normalform) \newline + Die Normalform eines $\lambda$-Terms ist - sofern sie existiert - eindeutig. \color{black} + \newline + \newline + Beweis: + \begin{itemize*} + \item $t_1$ und $t_2$ Normalformen von t, d.h. $t \stackrel{*}{\Rightarrow} t_1 \nRightarrow$ und $t \stackrel{*}{\Rightarrow} t_2 \nRightarrow$ + \item Nach Chruch-Rosser gibt es t' mit $t_1 \stackrel{*}{\Rightarrow} t'$ und $t_2 \stackrel{*}{\Rightarrow} t'$ + \item Nach Annahme $t_1 \nRightarrow$ und $t_2 \nRightarrow$, also $t_1 = t' = t_2$ + \end{itemize*}\ \newline + \color{blue} + Bei terminierenden $\beta$-Reduktionen ist irrelevant, welchen Redex man zuerst reduziert!\color{black} + \subsection{Auswertung von Parametern in Programmiersprachen} + \subsubsection{Behandlung von Parametern in Programmiersprachen} + \begin{itemize*} + \item Die Art und Weise, wie in einer Programmiersprache Parametter übergeben - d.h. wie die Reihenfolge und die Zeitpunkte ihrer Auswertung gehandhabt - werden, hat Einfluss auf wichtige Eigenschaften der Sprache: + \begin{itemize*} + \item Effizienz der Berechnungen + \item Termininerungsverhalten + \item Ausdruckskraft + \end{itemize*} + \item Hierbei ist es insbesondere von Interesse, wie Parameter gehandhabt werden, deren Werte undefiniert sind (z.B. 1/0)\newline + \colorbox{lightgray}{ + \begin{minipage}[h]{.9\linewidth} + Wir definieren zunächst den zentralen begriff 'strikt': \newline Eine n-stellige Funktion heißt strikt im k-ten Argument $(1<=k<=n)$, wenn gilt: $f(x_1,x_2,...,x_{k-1},\bot,x_{k+1},...,x_n)=\bot$ + \end{minipage}} + \item Ein undefiniertes Argument führt hier zu einem undefinierten Resultat + \item Grundsätzlich kann man die Auswertungsstrategien von Programmiersprachen in strikte und nicht-strikte Strategien einteilen; sehr gebräuchlich sind dabei insbesondere: + \begin{itemize*} + \item Call by Value: Ausdrücke, die Parameter bei einem Funktionsaufruf beschreiben, werden vor der Übergabe an die Funktion vollständig ausgewertet + \item Call by Name: Ausdrücke, die Parameter bei einem Funktionsaufruf beschreiben, werden nicht bei Übergabe, sondern erst dann ausgewertet, wenn sie in der aufgerufenen Funktion tatsächlich benötigt werden + \end{itemize*} + \item Beide Varianten haben spezifische Vor- und Nachteile: + \begin{itemize*} + \item Call by Value: weniger Berechnungsaufwand, wenn ein Parameter mehr als einmal im Funktionsrumpf vorkommt; weniger Speicheraufwand bei der Übergabe + \item Call by Name: weniger Berechnungsaufwand, wenn ein Argument nicht zum Ergebnis beiträgt; höherer Aufwand bei Übergabe + \end{itemize*} + \item Die Programmiersprache Erlang realisiert grundsätzlich eine strikte Handhabung von Parametern, da sie die Strategie Call by Value verwendet + \item Allerdings wird bei der Definition einer Funktion der resultierende Wert erst dann berechnet, wenn die Funktion ausgewertet wird + \begin{itemize*} + \item Das erlaubt über den Umweg zusätzlicher Funktionsdefinitionen auch die Realisierung einer nicht-strikten Auswertungsstrategie - ermöglicht Nachbildung der sogenannten Lazy-Evaluation + \item hierbei wird ein nicht-strikt zu evaluierendes Argument als Resultat einer anonymen nullstelligen Funktion (ohne Parameter) 'verpackt' + \item Im Rumpf der eigentlichen Funktion wird diese Funktion dann ausgewertet (= aufgerufen), wenn feststeht, dass dieses Argument für die Berechnung des Ergebnisses benötigt wird + \item Andere funktionale Sprachen wie Haskell oder Gofer verwenden Call by Name und realisieren damit grundsätzlich Lazy-Evaluation + \end{itemize*} + + \begin{lstlisting}[language=erlang] -module(lazy). -export([test1/3, test2/3]). test1(P, A, B) -> % A and B are arbitrary values @@ -2840,493 +2843,493 @@ Bei terminierenden $\beta$-Reduktionen ist irrelevant, welchen Redex man zuerst P==false -> B() end. \end{lstlisting} - - \begin{lstlisting} + + \begin{lstlisting} > lazy:test1(true, 3, 4/0). ** exception error: bad argument in an arithmetic expression in operator '/'/2 called as 4 / 0 > lazy:test2(true, fun() -> 3 end, fun() -> 4/0 end). 3 \end{lstlisting} - - \item Erläuterungen: - \begin{itemize*} - \item Im zweiten Beispiel wird der Rückgabewert der übergebenen Funktionne nur ausgewertet, wenn sie im Rumpf der auszuführenden Funktion aufgerufen werden - \item Innerhalb von Erlang-Modulen kann man sich mit Hilfe einer Macro-Definition Schreibarbeit sparen: - \begin{lstlisting}[language=erlang] + + \item Erläuterungen: + \begin{itemize*} + \item Im zweiten Beispiel wird der Rückgabewert der übergebenen Funktionne nur ausgewertet, wenn sie im Rumpf der auszuführenden Funktion aufgerufen werden + \item Innerhalb von Erlang-Modulen kann man sich mit Hilfe einer Macro-Definition Schreibarbeit sparen: + \begin{lstlisting}[language=erlang] -define(DELAY(E), fun() -> E end). check() -> test2(true, ?DELAY(3), ?DELAY(4/0)). \end{lstlisting} + \end{itemize*} + \item Je nachdem, ob und wie häufig ein übergebener Parameter im Funktionsrumpf benötigt wird, können bei Lazy-Evaluation Berechnungen + \begin{itemize*} + \item komplett eingespart oder + \item (in identischer Form) wiederholt erforderlich werden + \item Unter Umständen kann man in der betreffenden Funktion durch Einführung einer temporären Variable redundante Mehrfachberechnungen einsparen ($\rightarrow$ Call by Need) + \end{itemize*} + \item Die Parameterübergabe ist bei Call by Name in der Regel aufwändiger als bei Call by Value + \begin{itemize*} + \item Die meisten Programmiersprachen (Java, C, C++, Pascal etc.) verwenden daher Call by Value ($\rightarrow$ strikte Auswertung) + \item Eine Ausnahme wird oft bei dem IF-Konstrukt gemacht (der auszuführende Code ist hier ja meist auch kein Parameter) + \end{itemize*} + \item Zu Ausdrucksstärke: während strikte Funktionen durch die Strategie Call by Value realisiert werden, ist es nicht so, dass Lazy Evaluation es erlaubt, alle nicht-strikten Funktionen zu realisieren + \begin{itemize*} + \item Die folgenden Gleichungen definieren eine nicht-strikte Multiplikation $\otimes$ auf der Basis der Multiplikation · für Zahlen: + $$0 \otimes y = 0$$ + $$x \otimes 0 = 0$$ + $$x \otimes y = x * y$$ + \item Wenn ein Arguemnt undefiniert ist, dann liefert $\otimes$ ein Ergebnis, sofern das andere Argument zu 0 evaluiert wird ($\rightarrow fak(-1) \otimes (fak(3)-6)$) + \item Implementiert werden kann die Funktion nur durch eine Art von paralleler Auswertung mit Abbruch der anderen Berechnung sobald 0 als Resultat berechnet und zurückgegeben wurde + \end{itemize*} + \item Wir betrachten nun die Beziehungen zwischen Parameterbehandlung in Programmiersprachen und Reduktion von Lambda-Termen \end{itemize*} - \item Je nachdem, ob und wie häufig ein übergebener Parameter im Funktionsrumpf benötigt wird, können bei Lazy-Evaluation Berechnungen + + \subsubsection{Auswertungsstrategien \& Programmiersprachen} + \color{blue} Werte in Programmiersprachen wie Haskell: \color{black} \begin{itemize*} - \item komplett eingespart oder - \item (in identischer Form) wiederholt erforderlich werden - \item Unter Umständen kann man in der betreffenden Funktion durch Einführung einer temporären Variable redundante Mehrfachberechnungen einsparen ($\rightarrow$ Call by Need) + \item Primitive Werte: 2, True + \item Funktionen: ($\backslash x \rightarrow x$), ($\&\&$), ($x\rightarrow(\backslash y\rightarrow y+y)x$) \end{itemize*} - \item Die Parameterübergabe ist bei Call by Name in der Regel aufwändiger als bei Call by Value - \begin{itemize*} - \item Die meisten Programmiersprachen (Java, C, C++, Pascal etc.) verwenden daher Call by Value ($\rightarrow$ strikte Auswertung) - \item Eine Ausnahme wird oft bei dem IF-Konstrukt gemacht (der auszuführende Code ist hier ja meist auch kein Parameter) + + \color{blue} Werte im $\lambda$-Kalkül: + \color{black} \begin{itemize*} + \item Abstraktionen: $c_2 = \lambda s. \lambda z.s\;(s\;z),\;\;\; C_{true} = \lambda t- \lambda f.t,\;\;\; \lambda x.x,\;\;\; \newline \lambda b_1. \lambda b_2.\;b_1\; b_2 (\lambda t.\lambda f.\;f),\;\;\; \lambda x.\;(\lambda y.\;plus\; yy)x$ \end{itemize*} - \item Zu Ausdrucksstärke: während strikte Funktionen durch die Strategie Call by Value realisiert werden, ist es nicht so, dass Lazy Evaluation es erlaubt, alle nicht-strikten Funktionen zu realisieren - \begin{itemize*} - \item Die folgenden Gleichungen definieren eine nicht-strikte Multiplikation $\otimes$ auf der Basis der Multiplikation · für Zahlen: - $$0 \otimes y = 0$$ - $$x \otimes 0 = 0$$ - $$x \otimes y = x * y$$ - \item Wenn ein Arguemnt undefiniert ist, dann liefert $\otimes$ ein Ergebnis, sofern das andere Argument zu 0 evaluiert wird ($\rightarrow fak(-1) \otimes (fak(3)-6)$) - \item Implementiert werden kann die Funktion nur durch eine Art von paralleler Auswertung mit Abbruch der anderen Berechnung sobald 0 als Resultat berechnet und zurückgegeben wurde - \end{itemize*} - \item Wir betrachten nun die Beziehungen zwischen Parameterbehandlung in Programmiersprachen und Reduktion von Lambda-Termen -\end{itemize*} - -\subsubsection{Auswertungsstrategien \& Programmiersprachen} -\color{blue} Werte in Programmiersprachen wie Haskell: \color{black} -\begin{itemize*} - \item Primitive Werte: 2, True - \item Funktionen: ($\backslash x \rightarrow x$), ($\&\&$), ($x\rightarrow(\backslash y\rightarrow y+y)x$) -\end{itemize*} - -\color{blue} Werte im $\lambda$-Kalkül: -\color{black} \begin{itemize*} - \item Abstraktionen: $c_2 = \lambda s. \lambda z.s\;(s\;z),\;\;\; C_{true} = \lambda t- \lambda f.t,\;\;\; \lambda x.x,\;\;\; \newline \lambda b_1. \lambda b_2.\;b_1\; b_2 (\lambda t.\lambda f.\;f),\;\;\; \lambda x.\;(\lambda y.\;plus\; yy)x$ -\end{itemize*} -\color{blue} Auswertungsstrategie: \color{black} Keine weitere Reduzierung von Werten \newline -Reduziere keine Redexe unter Abstraktionen (umgeben von $\lambda$):\newline + \color{blue} Auswertungsstrategie: \color{black} Keine weitere Reduzierung von Werten \newline + Reduziere keine Redexe unter Abstraktionen (umgeben von $\lambda$):\newline $\Rightarrow$ call-by-name, call-by-value -\subsubsection{Call-By-Name} -Call-By-Name: Reduziere linkesten äußersten Redex -\begin{itemize*} - \item Aber nicht falls von einem $\lambda$ umgeben \newline - \subitem $(\lambda y. (\lambda x.y(\lambda z.z)x))\color{red} ((\lambda x.x)(\lambda y.y))$ \color{black} \newline - \subitem $(\lambda x.((\lambda \color{blue}x.x\color{black}) \color{red}(\lambda y.y)\color{black})(\lambda z.z)x)$ + \subsubsection{Call-By-Name} + Call-By-Name: Reduziere linkesten äußersten Redex + \begin{itemize*} + \item Aber nicht falls von einem $\lambda$ umgeben \newline + \subitem $(\lambda y. (\lambda x.y(\lambda z.z)x))\color{red} ((\lambda x.x)(\lambda y.y))$ \color{black} \newline + \subitem $(\lambda x.((\lambda \color{blue}x.x\color{black}) \color{red}(\lambda y.y)\color{black})(\lambda z.z)x)$ + \begin{center} + \item Intuition: Reduziere Argumente erst, wenn benötigt + \end{center} + \end{itemize*} + + Auswertung in Haskell: \color{blue} Lazy-Evaluation = call-by-name (+sharing)\color{black} + \begin{itemize*} + \item Standard-Auswertungsstrategie für Funktionen/Konstruktoren + \item listOf x = x : listOf x + \item 3: listOf 3 $\nRightarrow$ + \item (div 1 0) : (6 : []) + \item tail ((div 1 0): (6 : [])) $\Rightarrow$ 6 : [] $\nRightarrow$ + \end{itemize*} + + \subsubsection{Call-By-Value} + Call-By-Value: Reduziere linkesten Redex + \begin{itemize*} + \item der nicht einen $\lambda$ umgibt + \item und dessen Argument ein \color{blue} Wert \color{black} ist + \subsubitem $(\lambda y.(\lambda x.y(\lambda z.z)x)((\lambda \color{blue} x.x \color{black})\color{red}(\lambda y.y)\color{black})$ + \subsubitem $\Rightarrow (\lambda \color{blue}y\color{black}(\lambda x. \color{blue}y\color{black}(\lambda z.z)x))\color{red}(\lambda y.y)\color{black}$ + \subsubitem $\Rightarrow (\lambda x.(\lambda y.y(\lambda z.z)x)) \nRightarrow$ + \item Intuition: Argumente vor Funktionsaufruf auswerten + \item Auswertungsstrategie vieler Sprachen: Java, C, Scheme, ML, ... + \item Arithmetik in Haskell: Auswertung by-value + \item $prodOf(x) = y * prodOf x$ + \item $3 * prodOf 3 \Rightarrow 3 * (3 * prodOf 3) \Rightarrow$ ... + \item $((div \space1 \space 0) * 6) * 0 \Rightarrow \bot$ + \item $((div \space2 \space 2) * 6) * 0 \Rightarrow ((1 * 6) * 0) \Rightarrow 6 * 0\Rightarrow 0 \nRightarrow$ + \end{itemize*} + + \subsubsection{Vergleich der Auswertungsstrategien} + \color{blue} Call-by-name vs. Call-by-value \color{black} + \begin{itemize*} + \item Werten nicht immer zur Normalform aus $\lambda x.(\lambda y.y)x$ + \item Gibt es Normalform, dann darauf $\beta$-reduzierbar (Church-Rosser) + \item Call-by-name terminiert öfter + \item $Y(\lambda y.z) = \lambda f. (\lambda x.f(x\;x))(\lambda x.f(x\;x))\color{red}(\lambda y.z)\color{black}$ \newline + \subitem $ \Rightarrow \lambda x.(\lambda y.z(x\;x))\color{red} (\lambda x.(\lambda y.z)(x\;x))$ \newline + \subitem $\Rightarrow (\lambda y.z)\color{red}((\lambda x.(\lambda y.z)(x\;x)) (\lambda x.(\lambda y.z (x\;x)))\color{black} \stackrel{cbn}{\Rightarrow} z$ + \subitem \newline + \subitem $\stackrel{cbv}{\Rightarrow} (\lambda y.z)((\lambda x.(\lambda y.z)(x\;x))\color{red}(\lambda x.(\lambda y.z (x\;x))\color{black})$ + \subitem $\stackrel{cbv}{\Rightarrow} (\lambda y.z)((\lambda y.z)((\lambda x.(\lambda y.z)(x\;x)) \color{red} (\lambda x.(\lambda y.z(x\;x)) \color{black}))$ + \end{itemize*} + \color{blue} Standardisierungssatz \newline + Wenn t eine Normalform hat, dann findet Normalreihenfolgenauswertung diese. + \color{black} + + \subsubsection{Abschließende Bemerkungen} + \begin{itemize*} + \item Der Lambda-Kalkül wurde in den dreißiger Jahren des 20. Jahrhunderts von Alonzo Church erfunden, um damit grundsätzliche Betrachtungen über berechenbare Funktionen anzustellen + \item Trotz der Einfachheit der dem Kalkül zugrunde liegenden Regeln, realisiert er ein universelles Berechnungsmodell + \item Der Lambda-Kalkül hat die Entwicklung zahlreicher, für die Informatik wichtiger Konzepte beeinflusst + \begin{itemize*} + \item Funktionale Programmiersprachen (die minimalen Funktionen von Lisp wurden auf Grundlage des Lambda-Kalküls definiert) + \item Forschund zu Typsystemen für Programmiersprachen + \item Repräsentation von Logik-Termen im Lambda-Kalkül führte zu Theorembeweisen für Logiken höherer Stufen + \end{itemize*} + \item Manche Puristen vertreten gelegentlich die Ansicht, dass funktionale Programmiersprachen nicht viel mehr sind, als 'Lambda-Kalkül mit etwas syntaktischem Zucker' + \end{itemize*} + + \subsection{Zusammenfassung} + \begin{itemize*} + \item Funktionale Programmierung folgt einem verallgemeinerten Konzept der Funktionsauswertung + \item Die Programmiersprache Erlang ist dynamisch typisiert und unterstützt auch Funktionen höherer Ordnung + \item Manche Algorithmen lassen sich in Erlang aufgrund der mächtigen Listenkonstrukte und des flexiblen Pattern Matching sehr kompakt formulieren ($\rightarrow$ Potenzmenge, Quicksort) + \item Das heißt jedoch nicht, dass sehr kompakter Code auch zu sehr effizientem Laufzeit- und/oder Speicherbedarf führt - teilweise muss der Code relativ geschickt optimiert werden, um einigermaßen effiziente Lösungen zu erhalten ($\rightarrow$ Quicksort) + \item Manche Aufgaben, die in imperativen Programmiersprachen sehr effizient und einfach lösbar sind ($\rightarrow$ Teilen einer Liste in gleich große Hälften) sind mittels Listen nur recht umständlich und aufwendig lösbar + \item Es gilt in der Praxis also abzuwägen, für welche Aufgaben eine funktionale Sprache eingesetzt werden soll + \end{itemize*} + + \newpage + \section{Multithreading und parallele Programmierung} + \subsection{Grundlagen} + \subsubsection{Lernziele} + \begin{itemize*} + \item Programmierung paralleler Algorithmen und Verfahren als Paradigma + \item Verständnis grundlegender Architekturen und Modelle + \item Praktische Erfahrungen mit Erlang, Java und C++ + \end{itemize*} + + \subsubsection{Grundbegriffe} + \begin{itemize*} + \item Prozess := Programm in Ausführung; Ausführungsumgebung für ein Programm; hat eigenen Adressraum; Prozessor kann immer nur einen Prozess ausführen + \item Thread ('Faden') := leichtgewichtige Ausführungseinheit oder Kontrollfluss (Folge von Anweisungen) innerhalb eines sich in Ausführung befindlichen Programms; 'leichtgewichtig' im Vergleich zu Betriebssystemprozess; Threads eines Prozesses teilen sich Adressraum; Thread kann von CPU oder Core ausgeführt werden + \item Shared Memory := Kommunikation (über Variablen im) gemeinsamen Speicher; Prozess kann direkt auf Speicher eines anderen Prozesses zugreifen; erfordert explizite Synchronisation, z.B. über kritische Abschnitte + \item Message Passing := Prozesse mit getrennten Adressräumen; Zugriff nur auf eigenen Speicher; Kommunikation durch explizites Senden/Empfangen von Nachrichten; implizite Synchronisation durch Nachrichten + \item Parallelisierungsarten + \begin{itemize*} + \item Instruktionsparallelität: parallele Ausführung mehrerer Operationen durch eine CPU-Instruktion; explizit: Vektorinstruktionen, SIMD; implizit: Pipelining von Instruktionen + \item Taskparallelität: Ausnutzung inhärenter Parallelität durch simultane Ausführung unabhängiger Aufgaben + \item Datenparallelität: Gemeinsame Operation auf homogener Datenmenge; Zerlegung eines Datensatzes in kleinere Abschnitte + \end{itemize*} + \end{itemize*} + + \subsubsection{The free launch is over} + Taktfrequenz wächst nur noch langsam + \begin{itemize*} + \item Physikalische Gründe: Wärmeentwicklung, Energiebedarf, Kriechströme,... + \end{itemize*} + Auswege + \begin{itemize*} + \item Hyperthreading: + \begin{itemize*} + \item Abarbeitung mehrerer Threads auf einer CPU (5-15 \% Performancegewinn) + \item Einfache Hardwareunterstützung (einige Register) + \end{itemize*} + \item Multicore: + \begin{itemize*} + \item Mehrere CPUs auf einem Chip + \item Billiger als echte Mehrprozessorsysteme + \end{itemize*} + \item Caching: + \begin{itemize*} + \item Vergrößerung L1, L2, L3 Cache + \item Speicherzugriff 10-50 $\cdot$ teurer als Cachezugriff + \end{itemize*} + \end{itemize*} + + \subsubsection{Konsequenzen und Trends} + \begin{itemize*} + \item Applikationen müssen nebenläufig programmiert werden um CPU auszunutzen $\rightarrow$ Many-Core-Systeme + \item CPU-Begrenzung von Applikationen + \item Effizienz und Performanceoptimierung werden immer wichtiger + \item Unterstützung von Nebenläufigkeit/Parallelität durch Programmiersprachen + \end{itemize*} + + \begin{figure}[!tbp] + \centering + \begin{minipage}[b]{0.45\textwidth} + \includegraphics[width=1.0\linewidth]{Assets/Programmierparadigmen-einordnung-programmierung} + \caption{Einordnung} + \end{minipage} + \hfill + \begin{minipage}[b]{0.45\textwidth} + \includegraphics[width=1.0\linewidth]{Assets/Programmierparadigmen-Architekturen} + \caption{Architekturen: SIMD, SMP, NUMA, Cluster, Grid} + \end{minipage} + \vfill + \begin{minipage}[b]{0.45\textwidth} + \includegraphics[width=1.0\linewidth]{Assets/Programmierparadigmen-Multiprozessorsysteme} + \caption{Multiprozessorsysteme} + \begin{itemize*} + \item Zugriff über Bus auf gemeinsamen Speicher + \item jeder Prozessor mit eigenen Caches + \end{itemize*} + \end{minipage} + \hfill + \begin{minipage}[b]{0.45\textwidth} + \includegraphics[width=1.0\linewidth]{Assets/Programmierparadigmen-Multicore-Systeme} + \caption{Multicore-Systeme} + \begin{itemize*} + \item mehrere Prozessorkerne auf einem Chip + \item Kerne typischerweise mit eigenen L1/L2-Caches und gemeinsamen L3-Cache + \end{itemize*} + \end{minipage} + \vfill + \begin{minipage}[b]{0.45\textwidth} + \includegraphics[width=1.0\linewidth]{Assets/Programmierparadigmen-SMP} + \caption{SMP (Symmetric Multi Processing)} + \begin{itemize*} + \item Speicherbandbreite begrenzt und von allen Prozessoren gemeinsam genutzt + \item Skalierbarkeit begrenzt + \item Single Socket Lösung + \end{itemize*} + \end{minipage} + \hfill + \begin{minipage}[b]{0.45\textwidth} + \includegraphics[width=1.0\linewidth]{Assets/Programmierparadigmen-NUMA} + \caption{NUMA (Non-Uniform Memory Access)} + \begin{itemize*} + \item jedem Prozessor sind Teile des Speichers zugeordnet + \item lokaler Zugriff ist schneller als entfernter + \item Typisch für Multi-Socket Systeme + \end{itemize*} + \end{minipage} + \end{figure} + + \subsubsection{Symmetrisch vs. Nicht-symmetrisch} + \begin{tabular}{c | c} + SMP (Symmetric Multi Processing) & NUMA (Non-Uniform Memory-Access) \\ + Speicherbandbreite begrenzt und von allen Prozessoren gemeinsam genutzt & jedem Prozessor sind Teile des Speichers zugeordnet \\ + Skalierbarkeit begrenzt & lokaler Zugriff ist schneller als entfernter \\ + Single Socket-Lösung & Mehr-Socket-Board \\ + \end{tabular} + + \subsubsection{CPU vs. GPU} + \begin{itemize*} + \item GPU = Graphics Processing Units + \item Hochparallele Prozessorarchitekturen (nicht nur) für Grafikrendering + \end{itemize*} \begin{center} - \item Intuition: Reduziere Argumente erst, wenn benötigt + \includegraphics[width=0.95\linewidth]{Assets/Programmierparadigmen-CPU-vs-GPU} \end{center} -\end{itemize*} - -Auswertung in Haskell: \color{blue} Lazy-Evaluation = call-by-name (+sharing)\color{black} -\begin{itemize*} - \item Standard-Auswertungsstrategie für Funktionen/Konstruktoren - \item listOf x = x : listOf x - \item 3: listOf 3 $\nRightarrow$ - \item (div 1 0) : (6 : []) - \item tail ((div 1 0): (6 : [])) $\Rightarrow$ 6 : [] $\nRightarrow$ -\end{itemize*} - -\subsubsection{Call-By-Value} -Call-By-Value: Reduziere linkesten Redex -\begin{itemize*} - \item der nicht einen $\lambda$ umgibt - \item und dessen Argument ein \color{blue} Wert \color{black} ist - \subsubitem $(\lambda y.(\lambda x.y(\lambda z.z)x)((\lambda \color{blue} x.x \color{black})\color{red}(\lambda y.y)\color{black})$ - \subsubitem $\Rightarrow (\lambda \color{blue}y\color{black}(\lambda x. \color{blue}y\color{black}(\lambda z.z)x))\color{red}(\lambda y.y)\color{black}$ - \subsubitem $\Rightarrow (\lambda x.(\lambda y.y(\lambda z.z)x)) \nRightarrow$ - \item Intuition: Argumente vor Funktionsaufruf auswerten - \item Auswertungsstrategie vieler Sprachen: Java, C, Scheme, ML, ... - \item Arithmetik in Haskell: Auswertung by-value - \item $prodOf(x) = y * prodOf x$ - \item $3 * prodOf 3 \Rightarrow 3 * (3 * prodOf 3) \Rightarrow$ ... - \item $((div \space1 \space 0) * 6) * 0 \Rightarrow \bot$ - \item $((div \space2 \space 2) * 6) * 0 \Rightarrow ((1 * 6) * 0) \Rightarrow 6 * 0\Rightarrow 0 \nRightarrow$ -\end{itemize*} - -\subsubsection{Vergleich der Auswertungsstrategien} -\color{blue} Call-by-name vs. Call-by-value \color{black} -\begin{itemize*} - \item Werten nicht immer zur Normalform aus $\lambda x.(\lambda y.y)x$ - \item Gibt es Normalform, dann darauf $\beta$-reduzierbar (Church-Rosser) - \item Call-by-name terminiert öfter - \item $Y(\lambda y.z) = \lambda f. (\lambda x.f(x\;x))(\lambda x.f(x\;x))\color{red}(\lambda y.z)\color{black}$ \newline - \subitem $ \Rightarrow \lambda x.(\lambda y.z(x\;x))\color{red} (\lambda x.(\lambda y.z)(x\;x))$ \newline - \subitem $\Rightarrow (\lambda y.z)\color{red}((\lambda x.(\lambda y.z)(x\;x)) (\lambda x.(\lambda y.z (x\;x)))\color{black} \stackrel{cbn}{\Rightarrow} z$ - \subitem \newline - \subitem $\stackrel{cbv}{\Rightarrow} (\lambda y.z)((\lambda x.(\lambda y.z)(x\;x))\color{red}(\lambda x.(\lambda y.z (x\;x))\color{black})$ - \subitem $\stackrel{cbv}{\Rightarrow} (\lambda y.z)((\lambda y.z)((\lambda x.(\lambda y.z)(x\;x)) \color{red} (\lambda x.(\lambda y.z(x\;x)) \color{black}))$ -\end{itemize*} -\color{blue} Standardisierungssatz \newline -Wenn t eine Normalform hat, dann findet Normalreihenfolgenauswertung diese. -\color{black} - -\subsubsection{Abschließende Bemerkungen} -\begin{itemize*} - \item Der Lambda-Kalkül wurde in den dreißiger Jahren des 20. Jahrhunderts von Alonzo Church erfunden, um damit grundsätzliche Betrachtungen über berechenbare Funktionen anzustellen - \item Trotz der Einfachheit der dem Kalkül zugrunde liegenden Regeln, realisiert er ein universelles Berechnungsmodell - \item Der Lambda-Kalkül hat die Entwicklung zahlreicher, für die Informatik wichtiger Konzepte beeinflusst + + \subsubsection{Flynn's Architekturklassifikation} + \begin{center} + \includegraphics[width=0.95\linewidth]{Assets/Programmierparadigmen-flynn-architekturklassifikation} + \end{center} + + \subsubsection{Maße zur Leistungsbewertung} \begin{itemize*} - \item Funktionale Programmiersprachen (die minimalen Funktionen von Lisp wurden auf Grundlage des Lambda-Kalküls definiert) - \item Forschund zu Typsystemen für Programmiersprachen - \item Repräsentation von Logik-Termen im Lambda-Kalkül führte zu Theorembeweisen für Logiken höherer Stufen + \item Maße für Laufzeitgewinn durch Parallelisierung + \item $T_n$ = Laufzeit des Programms mit n Prozessoren/Kernen + \item Speedup $Speedup = \frac{T_1}{T_n}$ + \item Effizienz $Effizienz = \frac{Speedup}{n}$ \end{itemize*} - \item Manche Puristen vertreten gelegentlich die Ansicht, dass funktionale Programmiersprachen nicht viel mehr sind, als 'Lambda-Kalkül mit etwas syntaktischem Zucker' -\end{itemize*} - -\subsection{Zusammenfassung} -\begin{itemize*} - \item Funktionale Programmierung folgt einem verallgemeinerten Konzept der Funktionsauswertung - \item Die Programmiersprache Erlang ist dynamisch typisiert und unterstützt auch Funktionen höherer Ordnung - \item Manche Algorithmen lassen sich in Erlang aufgrund der mächtigen Listenkonstrukte und des flexiblen Pattern Matching sehr kompakt formulieren ($\rightarrow$ Potenzmenge, Quicksort) - \item Das heißt jedoch nicht, dass sehr kompakter Code auch zu sehr effizientem Laufzeit- und/oder Speicherbedarf führt - teilweise muss der Code relativ geschickt optimiert werden, um einigermaßen effiziente Lösungen zu erhalten ($\rightarrow$ Quicksort) - \item Manche Aufgaben, die in imperativen Programmiersprachen sehr effizient und einfach lösbar sind ($\rightarrow$ Teilen einer Liste in gleich große Hälften) sind mittels Listen nur recht umständlich und aufwendig lösbar - \item Es gilt in der Praxis also abzuwägen, für welche Aufgaben eine funktionale Sprache eingesetzt werden soll -\end{itemize*} - -\newpage -\section{Multithreading und parallele Programmierung} -\subsection{Grundlagen} -\subsubsection{Lernziele} -\begin{itemize*} - \item Programmierung paralleler Algorithmen und Verfahren als Paradigma - \item Verständnis grundlegender Architekturen und Modelle - \item Praktische Erfahrungen mit Erlang, Java und C++ -\end{itemize*} - -\subsubsection{Grundbegriffe} -\begin{itemize*} - \item Prozess := Programm in Ausführung; Ausführungsumgebung für ein Programm; hat eigenen Adressraum; Prozessor kann immer nur einen Prozess ausführen - \item Thread ('Faden') := leichtgewichtige Ausführungseinheit oder Kontrollfluss (Folge von Anweisungen) innerhalb eines sich in Ausführung befindlichen Programms; 'leichtgewichtig' im Vergleich zu Betriebssystemprozess; Threads eines Prozesses teilen sich Adressraum; Thread kann von CPU oder Core ausgeführt werden - \item Shared Memory := Kommunikation (über Variablen im) gemeinsamen Speicher; Prozess kann direkt auf Speicher eines anderen Prozesses zugreifen; erfordert explizite Synchronisation, z.B. über kritische Abschnitte - \item Message Passing := Prozesse mit getrennten Adressräumen; Zugriff nur auf eigenen Speicher; Kommunikation durch explizites Senden/Empfangen von Nachrichten; implizite Synchronisation durch Nachrichten - \item Parallelisierungsarten + + \subsubsection{Amdahlsches Gesetz} \begin{itemize*} - \item Instruktionsparallelität: parallele Ausführung mehrerer Operationen durch eine CPU-Instruktion; explizit: Vektorinstruktionen, SIMD; implizit: Pipelining von Instruktionen - \item Taskparallelität: Ausnutzung inhärenter Parallelität durch simultane Ausführung unabhängiger Aufgaben - \item Datenparallelität: Gemeinsame Operation auf homogener Datenmenge; Zerlegung eines Datensatzes in kleinere Abschnitte + \item Berücksichtigung parallelisierbarer und serieller Anteile im Programmablauf + \item p = paralleler Anteil + \item s = serieller Anteil + \item n Prozessoren + \item $p+s = 1$ + \item Maximaler Speedup $Speedup_{max} = \frac{T_1}{T_n} = {\frac{s+p}{s+ \frac{p}{n}}} = \frac{1}{s+\frac{p}{n}}$ \end{itemize*} -\end{itemize*} - -\subsubsection{The free launch is over} -Taktfrequenz wächst nur noch langsam -\begin{itemize*} - \item Physikalische Gründe: Wärmeentwicklung, Energiebedarf, Kriechströme,... -\end{itemize*} -Auswege -\begin{itemize*} - \item Hyperthreading: + \begin{center} + \includegraphics[width=0.35\linewidth]{Assets/Programmierparadigmen-parallelisierung} + \includegraphics[width=0.35\linewidth]{Assets/Programmierparadigmen-amdahlsches-gesetz} + \end{center} + + \subsubsection{Prozesse und Threads} + Prozess := Programm in Ausführung; Ausführungsumgebung für ein Programm \begin{itemize*} - \item Abarbeitung mehrerer Threads auf einer CPU (5-15 \% Performancegewinn) - \item Einfache Hardwareunterstützung (einige Register) + \item hat eigenen Adressraum + \item Prozessor kann immer nur einen Prozess ausführen \end{itemize*} - \item Multicore: + Thread ('Faden') := leichtgewichtige Ausführungsreinheit oder Kontrollfluss (Folge von Anweisungen) innerhalb eines sich in Ausführung befindlichen Programms \begin{itemize*} - \item Mehrere CPUs auf einem Chip - \item Billiger als echte Mehrprozessorsysteme + \item leichtgewichtig im Vergleich zu Betriebssystemprozess + \item Threads eines Prozesses teilen sich den Adressraum + \item Thread kann von einer CPU oder einem Core ausgeführt werden \end{itemize*} - \item Caching: + + \subsubsection{Shared Memory vs Message Passing} + Art der Kommunikation zwischen Prozessen oder Threads + \paragraph{Shared Memory} \begin{itemize*} - \item Vergrößerung L1, L2, L3 Cache - \item Speicherzugriff 10-50 $\cdot$ teurer als Cachezugriff + \item Kommunikation (über Variable im) gemeinsamen Speicher + \item Prozess kann direkt auf Speicher eines anderen Prozesses zugreifen + \item erfordert explizite Synchronisation, z.B. über zeitkritische Abschnitte \end{itemize*} -\end{itemize*} - -\subsubsection{Konsequenzen und Trends} -\begin{itemize*} - \item Applikationen müssen nebenläufig programmiert werden um CPU auszunutzen $\rightarrow$ Many-Core-Systeme - \item CPU-Begrenzung von Applikationen - \item Effizienz und Performanceoptimierung werden immer wichtiger - \item Unterstützung von Nebenläufigkeit/Parallelität durch Programmiersprachen -\end{itemize*} - -\begin{figure}[!tbp] - \centering - \begin{minipage}[b]{0.45\textwidth} - \includegraphics[width=1.0\linewidth]{Assets/Programmierparadigmen-einordnung-programmierung} - \caption{Einordnung} - \end{minipage} - \hfill - \begin{minipage}[b]{0.45\textwidth} - \includegraphics[width=1.0\linewidth]{Assets/Programmierparadigmen-Architekturen} - \caption{Architekturen: SIMD, SMP, NUMA, Cluster, Grid} - \end{minipage} - \vfill - \begin{minipage}[b]{0.45\textwidth} - \includegraphics[width=1.0\linewidth]{Assets/Programmierparadigmen-Multiprozessorsysteme} - \caption{Multiprozessorsysteme} + \paragraph{Message Passing} + \begin{itemize*} + \item Prozesse mit getrennten Adressräumen; Zugriff nur auf eigenen Speicher + \item Kommunikation durch explizites Senden/Empfangen von Nachrichten + \end{itemize*} + + \begin{figure}[!tbp] + \centering + \begin{minipage}[b]{0.45\textwidth} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Shared-Memory-vs-Message-Passing} + \caption{Shared Memory vs Message Passing} + \end{minipage} + \hfill + \begin{minipage}[b]{0.45\textwidth} + \includegraphics[width=0.9\linewidth]{Assets/Programmierparadigmen-Programmiermodelle} + \caption{Programmiermodelle} + \end{minipage} + \end{figure} + + \subsubsection{Parallelisierungsarten} + Instruktionsparallelität: + \begin{description*} + \item[parallele Ausführung] mehrerer Operationen durch eine CPU-Instruktion + \item[explizit] Vektorinstruktionen, SIMD + \item[implizit] Pipelining von Instruktionen + \item[Taskparallelität] Ausnutzung inhärenter Parallelität durch simultane Ausführung unabhängiger Aufgaben + \item[Datenparalelität] \hfill \begin{itemize*} - \item Zugriff über Bus auf gemeinsamen Speicher - \item jeder Prozessor mit eigenen Caches + \item Gemeinsame Operation auf homogener Datenmenge + \item Zerlegung eines Datensatzes in kleinere Abschnitte \end{itemize*} - \end{minipage} - \hfill - \begin{minipage}[b]{0.45\textwidth} - \includegraphics[width=1.0\linewidth]{Assets/Programmierparadigmen-Multicore-Systeme} - \caption{Multicore-Systeme} + \end{description*} + + \begin{figure}[!tbp] + \centering + \begin{minipage}[b]{0.45\textwidth} + \includegraphics[width=0.9\linewidth]{Assets/Programmierparadigmen-Instruktionsparallelität} + \caption{Instruktionsparallelität: SIMD} + \begin{itemize*} + \item Autovektorisierung durch Compiler + \item explizite Instruktionen + \item Beispiel: Addition zweier Vektoren + \end{itemize*} + \end{minipage} + \hfill + \begin{minipage}[b]{0.45\textwidth} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Taskparallelität} + \caption{Taskparallelität} + \begin{itemize*} + \item Unabhängikeit von Teilprozessen $\rightarrow$ Desequentialisierung + \item Beispiel: Quicksort + \end{itemize*} + \end{minipage} + \vfill + \begin{minipage}[b]{0.45\textwidth} + \includegraphics[width=0.9\linewidth]{Assets/Programmierparadigmen-Datenparallelität} + \caption{Datenparallelität} + \begin{itemize*} + \item homogene Datenmenge: Felder, Listen, Dokumentenmenge,... + \item Verteilung der Daten + \item alle Prozessoren führen gleiches Programm auf jeweils eigenen Daten aus + \item Beispiel: Matrixaddition S = A + B + \end{itemize*} + \end{minipage} + \end{figure} + + \subsubsection{Herausforderungen} + \begin{itemize*} + \item Zerlegung eines Problems in parallel verarbeitbare Teile \begin{itemize*} - \item mehrere Prozessorkerne auf einem Chip - \item Kerne typischerweise mit eigenen L1/L2-Caches und gemeinsamen L3-Cache + \item Beispiel: Suche in einer Datenbank mit 1 TB Größe + \item Annahme: 100 MB/Sekunde mit einem Prozessor = 175 Minuten + \item bei paralleler Suche durch 10 Prozessoren = 17.5 Minuten + \item Übertragbar auf andere Probleme, z.B. Sortieren, Suche in Graphen? \end{itemize*} - \end{minipage} - \vfill - \begin{minipage}[b]{0.45\textwidth} - \includegraphics[width=1.0\linewidth]{Assets/Programmierparadigmen-SMP} - \caption{SMP (Symmetric Multi Processing)} + \item Synchronisation konkurrierender Zugriffe auf gemeinsame Ressourcen \begin{itemize*} - \item Speicherbandbreite begrenzt und von allen Prozessoren gemeinsam genutzt - \item Skalierbarkeit begrenzt - \item Single Socket Lösung + \item Beispiel: Produzent-Konsument-Beziehung + \item Annahme: Datenaustausch über gemeinsame Liste + \item Fragestellungen: Benachrichtigung über neues Element in der Liste, Konsument entnimmt Element während Produzent einfügt + \item Wechselseitiger Ausschluss \end{itemize*} - \end{minipage} - \hfill - \begin{minipage}[b]{0.45\textwidth} - \includegraphics[width=1.0\linewidth]{Assets/Programmierparadigmen-NUMA} - \caption{NUMA (Non-Uniform Memory Access)} + \item außerdem: Fehlersuche, Optimierung + \end{itemize*} + + \subsubsection{Zusammenfassung} + \begin{itemize*} + \item Parallele Verarbeitung als wichtiges Paradigma moderner Software + \item verschiedene parallele \begin{itemize*} - \item jedem Prozessor sind Teile des Speichers zugeordnet - \item lokaler Zugriff ist schneller als entfernter - \item Typisch für Multi-Socket Systeme + \item Hardwarearchitekturen und + \item Programmiermodelle \end{itemize*} - \end{minipage} -\end{figure} - -\subsubsection{Symmetrisch vs. Nicht-symmetrisch} -\begin{tabular}{c | c} - SMP (Symmetric Multi Processing) & NUMA (Non-Uniform Memory-Access) \\ - Speicherbandbreite begrenzt und von allen Prozessoren gemeinsam genutzt & jedem Prozessor sind Teile des Speichers zugeordnet \\ - Skalierbarkeit begrenzt & lokaler Zugriff ist schneller als entfernter \\ - Single Socket-Lösung & Mehr-Socket-Board \\ -\end{tabular} - -\subsubsection{CPU vs. GPU} -\begin{itemize*} - \item GPU = Graphics Processing Units - \item Hochparallele Prozessorarchitekturen (nicht nur) für Grafikrendering -\end{itemize*} -\begin{center} - \includegraphics[width=0.95\linewidth]{Assets/Programmierparadigmen-CPU-vs-GPU} -\end{center} - -\subsubsection{Flynn's Architekturklassifikation} -\begin{center} - \includegraphics[width=0.95\linewidth]{Assets/Programmierparadigmen-flynn-architekturklassifikation} -\end{center} - -\subsubsection{Maße zur Leistungsbewertung} -\begin{itemize*} - \item Maße für Laufzeitgewinn durch Parallelisierung - \item $T_n$ = Laufzeit des Programms mit n Prozessoren/Kernen - \item Speedup $Speedup = \frac{T_1}{T_n}$ - \item Effizienz $Effizienz = \frac{Speedup}{n}$ -\end{itemize*} - -\subsubsection{Amdahlsches Gesetz} -\begin{itemize*} - \item Berücksichtigung parallelisierbarer und serieller Anteile im Programmablauf - \item p = paralleler Anteil - \item s = serieller Anteil - \item n Prozessoren - \item $p+s = 1$ - \item Maximaler Speedup $Speedup_{max} = \frac{T_1}{T_n} = {\frac{s+p}{s+ \frac{p}{n}}} = \frac{1}{s+\frac{p}{n}}$ -\end{itemize*} -\begin{center} - \includegraphics[width=0.35\linewidth]{Assets/Programmierparadigmen-parallelisierung} - \includegraphics[width=0.35\linewidth]{Assets/Programmierparadigmen-amdahlsches-gesetz} -\end{center} - -\subsubsection{Prozesse und Threads} -Prozess := Programm in Ausführung; Ausführungsumgebung für ein Programm -\begin{itemize*} - \item hat eigenen Adressraum - \item Prozessor kann immer nur einen Prozess ausführen -\end{itemize*} -Thread ('Faden') := leichtgewichtige Ausführungsreinheit oder Kontrollfluss (Folge von Anweisungen) innerhalb eines sich in Ausführung befindlichen Programms -\begin{itemize*} - \item leichtgewichtig im Vergleich zu Betriebssystemprozess - \item Threads eines Prozesses teilen sich den Adressraum - \item Thread kann von einer CPU oder einem Core ausgeführt werden -\end{itemize*} - -\subsubsection{Shared Memory vs Message Passing} -Art der Kommunikation zwischen Prozessen oder Threads -\paragraph{Shared Memory} -\begin{itemize*} - \item Kommunikation (über Variable im) gemeinsamen Speicher - \item Prozess kann direkt auf Speicher eines anderen Prozesses zugreifen - \item erfordert explizite Synchronisation, z.B. über zeitkritische Abschnitte -\end{itemize*} -\paragraph{Message Passing} -\begin{itemize*} - \item Prozesse mit getrennten Adressräumen; Zugriff nur auf eigenen Speicher - \item Kommunikation durch explizites Senden/Empfangen von Nachrichten -\end{itemize*} - -\begin{figure}[!tbp] - \centering - \begin{minipage}[b]{0.45\textwidth} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Shared-Memory-vs-Message-Passing} - \caption{Shared Memory vs Message Passing} - \end{minipage} - \hfill - \begin{minipage}[b]{0.45\textwidth} - \includegraphics[width=0.9\linewidth]{Assets/Programmierparadigmen-Programmiermodelle} - \caption{Programmiermodelle} - \end{minipage} -\end{figure} - -\subsubsection{Parallelisierungsarten} -Instruktionsparallelität: -\begin{description*} - \item[parallele Ausführung] mehrerer Operationen durch eine CPU-Instruktion - \item[explizit] Vektorinstruktionen, SIMD - \item[implizit] Pipelining von Instruktionen - \item[Taskparallelität] Ausnutzung inhärenter Parallelität durch simultane Ausführung unabhängiger Aufgaben - \item[Datenparalelität] \hfill - \begin{itemize*} - \item Gemeinsame Operation auf homogener Datenmenge - \item Zerlegung eines Datensatzes in kleinere Abschnitte - \end{itemize*} -\end{description*} - -\begin{figure}[!tbp] - \centering - \begin{minipage}[b]{0.45\textwidth} - \includegraphics[width=0.9\linewidth]{Assets/Programmierparadigmen-Instruktionsparallelität} - \caption{Instruktionsparallelität: SIMD} + \item Herausforderungen \begin{itemize*} - \item Autovektorisierung durch Compiler - \item explizite Instruktionen - \item Beispiel: Addition zweier Vektoren + \item Problemzerlegung + \item Synchronisation + \item ... \end{itemize*} - \end{minipage} - \hfill - \begin{minipage}[b]{0.45\textwidth} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Taskparallelität} - \caption{Taskparallelität} + \item im Weiteren: konkrete Methoden und Techniken in Erlang und C++ + \end{itemize*} + + \subsection{Parallele Programmierung in Erlang} + \subsubsection{Unterstützung paralleler Programmierung in Erlang} + \begin{itemize*} + \item Leichtgewichtige Prozesse und Message Passing + \item SMP-Support (Symmetric Multi Processing) + \item Ziele für effiziente Parallelisierung \begin{itemize*} - \item Unabhängikeit von Teilprozessen $\rightarrow$ Desequentialisierung - \item Beispiel: Quicksort + \item Problem in viele Prozesse zerlegen (aber nicht zu viele ...) + \item Seiteneffekte vermeiden (würde Synchronisation erfordern ...) + \item Sequentiellen Flaschenhals vermeiden (Zugriff auf gemeinsame Ressourcen: IO, Registrierung von Prozessen, ...) -> Small Messages, Big Computation! \end{itemize*} - \end{minipage} - \vfill - \begin{minipage}[b]{0.45\textwidth} - \includegraphics[width=0.9\linewidth]{Assets/Programmierparadigmen-Datenparallelität} - \caption{Datenparallelität} + \end{itemize*} + + \subsubsection{Prozesse in Erlang} + \begin{itemize*} + \item Erlang VM = Betriebssystemprozess + \item Erlang-Prozess = Thread innerhalb der Erlang VM \begin{itemize*} - \item homogene Datenmenge: Felder, Listen, Dokumentenmenge,... - \item Verteilung der Daten - \item alle Prozessoren führen gleiches Programm auf jeweils eigenen Daten aus - \item Beispiel: Matrixaddition S = A + B + \item kein Zugriff auf gemeinsame Daten, daher 'Prozess' \end{itemize*} - \end{minipage} -\end{figure} - -\subsubsection{Herausforderungen} -\begin{itemize*} - \item Zerlegung eines Problems in parallel verarbeitbare Teile - \begin{itemize*} - \item Beispiel: Suche in einer Datenbank mit 1 TB Größe - \item Annahme: 100 MB/Sekunde mit einem Prozessor = 175 Minuten - \item bei paralleler Suche durch 10 Prozessoren = 17.5 Minuten - \item Übertragbar auf andere Probleme, z.B. Sortieren, Suche in Graphen? - \end{itemize*} - \item Synchronisation konkurrierender Zugriffe auf gemeinsame Ressourcen - \begin{itemize*} - \item Beispiel: Produzent-Konsument-Beziehung - \item Annahme: Datenaustausch über gemeinsame Liste - \item Fragestellungen: Benachrichtigung über neues Element in der Liste, Konsument entnimmt Element während Produzent einfügt - \item Wechselseitiger Ausschluss - \end{itemize*} - \item außerdem: Fehlersuche, Optimierung -\end{itemize*} - -\subsubsection{Zusammenfassung} -\begin{itemize*} - \item Parallele Verarbeitung als wichtiges Paradigma moderner Software - \item verschiedene parallele - \begin{itemize*} - \item Hardwarearchitekturen und - \item Programmiermodelle - \end{itemize*} - \item Herausforderungen - \begin{itemize*} - \item Problemzerlegung - \item Synchronisation - \item ... - \end{itemize*} - \item im Weiteren: konkrete Methoden und Techniken in Erlang und C++ -\end{itemize*} - -\subsection{Parallele Programmierung in Erlang} -\subsubsection{Unterstützung paralleler Programmierung in Erlang} -\begin{itemize*} - \item Leichtgewichtige Prozesse und Message Passing - \item SMP-Support (Symmetric Multi Processing) - \item Ziele für effiziente Parallelisierung - \begin{itemize*} - \item Problem in viele Prozesse zerlegen (aber nicht zu viele ...) - \item Seiteneffekte vermeiden (würde Synchronisation erfordern ...) - \item Sequentiellen Flaschenhals vermeiden (Zugriff auf gemeinsame Ressourcen: IO, Registrierung von Prozessen, ...) -> Small Messages, Big Computation! - \end{itemize*} -\end{itemize*} - -\subsubsection{Prozesse in Erlang} -\begin{itemize*} - \item Erlang VM = Betriebssystemprozess - \item Erlang-Prozess = Thread innerhalb der Erlang VM - \begin{itemize*} - \item kein Zugriff auf gemeinsame Daten, daher 'Prozess' - \end{itemize*} - \item jede Erlang-Funktion kann einen Prozess bilden - \item Funktion spawn erzeugt einen Prozess, der die Funktion Fun ausführt - \begin{lstlisting} + \item jede Erlang-Funktion kann einen Prozess bilden + \item Funktion spawn erzeugt einen Prozess, der die Funktion Fun ausführt + \begin{lstlisting} Pid = spawn(fun Fun/0) \end{lstlisting} - \item Resultat = Prozessidentifikation Pid, mittels der man dem Prozess Nachrichten schicken kann. - \item über self() kann man die eigene Pid ermitteln - \item Übergabe von Arghumenten an den Prozess bei der Erzeugung - \begin{lstlisting} + \item Resultat = Prozessidentifikation Pid, mittels der man dem Prozess Nachrichten schicken kann. + \item über self() kann man die eigene Pid ermitteln + \item Übergabe von Arghumenten an den Prozess bei der Erzeugung + \begin{lstlisting} Pid = spawn(fun() -> any_func(Arg1, Arg2, ...) end) \end{lstlisting} -\end{itemize*} - -\begin{figure}[!tbp] - \centering - \begin{minipage}[b]{0.45\textwidth} - \includegraphics[width=0.9\linewidth]{Assets/Programmierparadigmen-erlang-Beispiele} - \caption{Beispiele} - \end{minipage} - \hfill - \begin{minipage}[b]{0.45\textwidth} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-SMP Erlang} - \caption{SMP Erlang} - \end{minipage} -\end{figure} - -\begin{itemize*} - \item 4 Betriebssystemthreads (hier 2 Kerne mit Hyperthreading) - \item kann mit -smp [disable $\mid$ enable $\mid$ auto] beeinflusst werden - \item +S [Anzahl] bestimmt Anzahl der Scheduler - \begin{itemize*} - \item sollte nicht größer als Anzahl der Kerne/Prozessoren sein \end{itemize*} -\end{itemize*} - -\subsubsection{Scheduler in Erlang} -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-erlang-scheduler} -\end{center} - -\subsubsection{Message Passing in Erlang: Senden einer Nachricht} -\begin{lstlisting} + + \begin{figure}[!tbp] + \centering + \begin{minipage}[b]{0.45\textwidth} + \includegraphics[width=0.9\linewidth]{Assets/Programmierparadigmen-erlang-Beispiele} + \caption{Beispiele} + \end{minipage} + \hfill + \begin{minipage}[b]{0.45\textwidth} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-SMP Erlang} + \caption{SMP Erlang} + \end{minipage} + \end{figure} + + \begin{itemize*} + \item 4 Betriebssystemthreads (hier 2 Kerne mit Hyperthreading) + \item kann mit -smp [disable $\mid$ enable $\mid$ auto] beeinflusst werden + \item +S [Anzahl] bestimmt Anzahl der Scheduler + \begin{itemize*} + \item sollte nicht größer als Anzahl der Kerne/Prozessoren sein + \end{itemize*} + \end{itemize*} + + \subsubsection{Scheduler in Erlang} + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-erlang-scheduler} + \end{center} + + \subsubsection{Message Passing in Erlang: Senden einer Nachricht} + \begin{lstlisting} Pid ! Message \end{lstlisting} -\begin{itemize*} - \item an Prozess Pid wird die Nachricht Message gesendet - \item der Prozess muss eine Empfangsoperation ausführen. damit ihn die Nachricht erreichen kann - \begin{lstlisting}[language=erlang] + \begin{itemize*} + \item an Prozess Pid wird die Nachricht Message gesendet + \item der Prozess muss eine Empfangsoperation ausführen. damit ihn die Nachricht erreichen kann + \begin{lstlisting}[language=erlang] receive Pattern1 [when Guard1] -> Expressions1; Pattern2 [when Guard2] -> Expressions2; ... end \end{lstlisting} - \item trifft eine Nachricht ein, wird versucht, diese mit einem Pattern und ggf. vorhandenen Guard zu 'matchen' - \item erstes zutreffendes Pattern (inkl. Guard) bestimmt, welcher Ausdruck ausgewertet wird - \item trifft kein Pattern zu, wird die Nachricht für spätere Verwendung aufgehoben und Prozess wartet auf die nächste Nachricht ($\rightarrow$ selective receive) -\end{itemize*} - - -\subsubsection{Ein einfacher Echo-Server} -\begin{lstlisting}[language=erlang] + \item trifft eine Nachricht ein, wird versucht, diese mit einem Pattern und ggf. vorhandenen Guard zu 'matchen' + \item erstes zutreffendes Pattern (inkl. Guard) bestimmt, welcher Ausdruck ausgewertet wird + \item trifft kein Pattern zu, wird die Nachricht für spätere Verwendung aufgehoben und Prozess wartet auf die nächste Nachricht ($\rightarrow$ selective receive) + \end{itemize*} + + + \subsubsection{Ein einfacher Echo-Server} + \begin{lstlisting}[language=erlang] -module(ch4_2). -export([run/0]). @@ -3343,27 +3346,27 @@ loop() -> stop -> true end. \end{lstlisting} - -Erklärungen -\begin{itemize*} - \item Funktion loop() realisiert einen (nur bedingt nützlichen) Echo-Dienst, der jede empfangene Nachricht unverändert an den Absender zurückschickt, bis er nach Empfang von stop endet - \item Funktion run() - \begin{enumerate*} - \item startet den Echoserver (Zeile 4) - \item schickt ihm als nächstes eine Nachricht (Zeile 5) - \item wartet auf eine Antwort (Zeile 6) - \item gibt diese aus (Zeile 7) - \item schickt dann stop an den Echoserver (Zeile 9) - \end{enumerate*} - \item Aufruf in der Funktion loop() erfolgt endrekursiv, daher wird kein wachsender Aufrufstapel angelegt (Hinweis: grundsätzlich zu beachten, da sonst der Speicherbedarf stetig wächst) -\end{itemize*} - -\subsubsection{Ansätze zur Parallelisierung} -\begin{itemize*} - \item Beispiel: Berechnung einer (zufällig generierten) Liste von Fibonaccizahlen - \item Sequentielle Lösung über lists:map/2 -\end{itemize*} -\begin{lstlisting}[language=erlang] + + Erklärungen + \begin{itemize*} + \item Funktion loop() realisiert einen (nur bedingt nützlichen) Echo-Dienst, der jede empfangene Nachricht unverändert an den Absender zurückschickt, bis er nach Empfang von stop endet + \item Funktion run() + \begin{enumerate*} + \item startet den Echoserver (Zeile 4) + \item schickt ihm als nächstes eine Nachricht (Zeile 5) + \item wartet auf eine Antwort (Zeile 6) + \item gibt diese aus (Zeile 7) + \item schickt dann stop an den Echoserver (Zeile 9) + \end{enumerate*} + \item Aufruf in der Funktion loop() erfolgt endrekursiv, daher wird kein wachsender Aufrufstapel angelegt (Hinweis: grundsätzlich zu beachten, da sonst der Speicherbedarf stetig wächst) + \end{itemize*} + + \subsubsection{Ansätze zur Parallelisierung} + \begin{itemize*} + \item Beispiel: Berechnung einer (zufällig generierten) Liste von Fibonaccizahlen + \item Sequentielle Lösung über lists:map/2 + \end{itemize*} + \begin{lstlisting}[language=erlang] % Berechnung der Fibonacci Zahl für F fibo(0) -> 0; fibo(1) -> 1; @@ -3375,14 +3378,14 @@ run(Num) -> Data = lists:map(fun(_) -> random:uniform(20) end, Seq), lists:map(fun fibo/1, Data) \end{lstlisting} - -\subsubsection{pmap: Parallele Funktionen höherer Ordnung} -\begin{itemize*} - \item Parallele Variante von lists:map(Fun, list) - \item für jedes Listenelement einen Prozess erzeugen - \item Ergebnisse einsammeln -\end{itemize*} -\begin{lstlisting}[language=erlang] + + \subsubsection{pmap: Parallele Funktionen höherer Ordnung} + \begin{itemize*} + \item Parallele Variante von lists:map(Fun, list) + \item für jedes Listenelement einen Prozess erzeugen + \item Ergebnisse einsammeln + \end{itemize*} + \begin{lstlisting}[language=erlang] pmap(F, L) -> S = self(), % Berechnung der Fibonacci Zahl für F Pids = lists:map(fun(I) -> @@ -3391,22 +3394,22 @@ pmap(F, L) -> end, L), % Ergebnisse einsammeln gather(Pids). \end{lstlisting} - -\paragraph{pmap: Hilfsfunktionen} - -\color{orange} Eigentliche Verarbeitungsfunktion ausführen \color{black} -\begin{lstlisting}[language=erlang] + + \paragraph{pmap: Hilfsfunktionen} + + \color{orange} Eigentliche Verarbeitungsfunktion ausführen \color{black} + \begin{lstlisting}[language=erlang] do_fun(Parent, F, I) -> % Parent ist der Elternprozess Parent ! { self(), (catch F(I))}. \end{lstlisting} -\begin{itemize*} - \item Funktion F aufrufen, catch sorgt für korrekte Behandlung von Fehlern in F - \item Ergebnis zusammen mit eigener Pid (self()) an Elternprozess senden -\end{itemize*} - -\color{orange} Einsammeln der Ergebnisse \color{black} -\begin{lstlisting}[language=erlang] + \begin{itemize*} + \item Funktion F aufrufen, catch sorgt für korrekte Behandlung von Fehlern in F + \item Ergebnis zusammen mit eigener Pid (self()) an Elternprozess senden + \end{itemize*} + + \color{orange} Einsammeln der Ergebnisse \color{black} + \begin{lstlisting}[language=erlang] gather([Pid | T]) -> receive % Ordnung der Ergebnisse entspricht Ordnung der Argumente @@ -3414,13 +3417,13 @@ gather([Pid | T]) -> end; gather([]) -> []. \end{lstlisting} -\begin{itemize*} - \item Zeile 5: Warten bis Paar (Pid, Ergebniswert) eintrifft - \item Zeile 7: Tail ist leer $\rightarrow$ alle Ergebnisse eingetroffen -\end{itemize*} - -\paragraph{Parallele Berechnung der Fibonacci-Zahlen} -\begin{lstlisting}[language=erlang] + \begin{itemize*} + \item Zeile 5: Warten bis Paar (Pid, Ergebniswert) eintrifft + \item Zeile 7: Tail ist leer $\rightarrow$ alle Ergebnisse eingetroffen + \end{itemize*} + + \paragraph{Parallele Berechnung der Fibonacci-Zahlen} + \begin{lstlisting}[language=erlang] %Liste von Num Fibonacci Zahlen run(Num) -> Seq = lists:seq(1, Num), % Zufallszahlen erzeugen @@ -3428,33 +3431,33 @@ run(Num) -> % Berechnung parallel ausführen pmap(fun fibo/1, Data). \end{lstlisting} - -\paragraph{Diskussion} - -\begin{itemize*} - \item Passende Abstraktion wählen + + \paragraph{Diskussion} + \begin{itemize*} - \item Ist Ordnung der Ergebnisse notwendig? - \item Werden Ergebnisse benötigt? + \item Passende Abstraktion wählen + \begin{itemize*} + \item Ist Ordnung der Ergebnisse notwendig? + \item Werden Ergebnisse benötigt? + \end{itemize*} + \item Anzahl der parallelen Prozesse + \begin{itemize*} + \item Abhängig von Berechnungsmodell, Hardware etc. + \item evtl. pmap mit max. Anzahl gleichzeitiger Prozesse + \end{itemize*} + \item Berechnungsaufwand der Prozesse + \begin{itemize*} + \item Berechnung vs. Daten/Ergebnisse senden + \end{itemize*} \end{itemize*} - \item Anzahl der parallelen Prozesse + + \paragraph{pmap: Alternative Implementierung} + \begin{itemize*} - \item Abhängig von Berechnungsmodell, Hardware etc. - \item evtl. pmap mit max. Anzahl gleichzeitiger Prozesse + \item ohne Berücksichtigung der Ordnung der Ergebnismenge + \item Zählen für die bereits eingetroffenen Ergebnisse \end{itemize*} - \item Berechnungsaufwand der Prozesse - \begin{itemize*} - \item Berechnung vs. Daten/Ergebnisse senden - \end{itemize*} -\end{itemize*} - -\paragraph{pmap: Alternative Implementierung} - -\begin{itemize*} - \item ohne Berücksichtigung der Ordnung der Ergebnismenge - \item Zählen für die bereits eingetroffenen Ergebnisse -\end{itemize*} -\begin{lstlisting}[language=erlang] + \begin{lstlisting}[language=erlang] pmap(F, L) -> ... gather2(length(L), Ref, []). @@ -3465,125 +3468,125 @@ run(Num) -> end; gather2(0,_, L) -> L. \end{lstlisting} - -\subsubsection{Speedup} -Bestimmung des Speedups erfordert -\begin{itemize*} - \item Zeitmessung - \item Kontrolle der genutzten Prozessoren/Cores -\end{itemize*} -\color{orange} Welchen Einfluss hat die Zahl der erzeugten Prozesse. \color{black} - -\paragraph{Speedup: Zeitmessung} - -Nutzung der Funktion timer:tc/3 -\begin{lstlisting} + + \subsubsection{Speedup} + Bestimmung des Speedups erfordert + \begin{itemize*} + \item Zeitmessung + \item Kontrolle der genutzten Prozessoren/Cores + \end{itemize*} + \color{orange} Welchen Einfluss hat die Zahl der erzeugten Prozesse. \color{black} + + \paragraph{Speedup: Zeitmessung} + + Nutzung der Funktion timer:tc/3 + \begin{lstlisting} > timer:tc(ch4_4, run, [30]). {7900,[233,1,987,610,377,8,144,89,89,3]} \end{lstlisting} -Für bessere Aussagekraft: mehrfache Ausführung -\begin{lstlisting}[language=erlang] + Für bessere Aussagekraft: mehrfache Ausführung + \begin{lstlisting}[language=erlang] benchmark(M, Fun, D) -> % 100 Funktionsaufrufe Runs = [timer:tc(M, Fun, [D]) || _ <- lists:seq(1, 100)], % Durchschnitt der Laufzeiten in Millisekunden berechnen lists:sum([T || {T, _ } <- Runs]) / (1000 * length(Runs)). \end{lstlisting} - -\paragraph{Bestimmung: Speedup} - -ch4\_6:benchmark(ch4\_4, run, 1000). -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Speedup-Bestimmung} -\end{center} -\color{orange} Achtung: \color{black} -\begin{itemize*} - \item Aufwand für Berechnung einer Fibonaccizahl ist nicht konstant - \item Zufallszahlen als Eingabe -\end{itemize*} - -\paragraph{Diskussion: Speedup} - -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Speedup-Diskussion} -\end{center} - -\subsubsection{Datenparallelität: Das Map-Reduce-Paradigma} -\begin{itemize*} - \item Parallelisierungsmuster inspiriert von Konzepten funktionaler Programmiersprachen (map,reduce/fold) - \item Basis von Big-Data-plattformen wie Hadoop, Spark,... - \item Grundidee: + + \paragraph{Bestimmung: Speedup} + + ch4\_6:benchmark(ch4\_4, run, 1000). + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Speedup-Bestimmung} + \end{center} + \color{orange} Achtung: \color{black} \begin{itemize*} - \item map(F, Seq) ? wende Funktion F (als Argument übergeben) auf alle Elemente einer Folge Seq an, + \item Aufwand für Berechnung einer Fibonaccizahl ist nicht konstant + \item Zufallszahlen als Eingabe + \end{itemize*} + + \paragraph{Diskussion: Speedup} + + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Speedup-Diskussion} + \end{center} + + \subsubsection{Datenparallelität: Das Map-Reduce-Paradigma} + \begin{itemize*} + \item Parallelisierungsmuster inspiriert von Konzepten funktionaler Programmiersprachen (map,reduce/fold) + \item Basis von Big-Data-plattformen wie Hadoop, Spark,... + \item Grundidee: \begin{itemize*} - \item Funktion F kann unabhängig (=parallel) auf jedes Element angewendet werden - \item Partitionieren und Verteilen der Elemente der Folge - \item z.B. multipliziere jedes Element mit 2 - \end{itemize*} - \item reduce(F, Seq) = wende eine Funktion F schrittweise auf die Elemente einer Folge Seq an und produziere einen einzelnen Wert, - \begin{itemize*} - \item prinzipiell ähnlich zu map(F, Seq), d.h. Funktion F kann auf Paare unabhängig angewendet werden - \item z.B. die Summe aller Elemente der Folge + \item map(F, Seq) ? wende Funktion F (als Argument übergeben) auf alle Elemente einer Folge Seq an, + \begin{itemize*} + \item Funktion F kann unabhängig (=parallel) auf jedes Element angewendet werden + \item Partitionieren und Verteilen der Elemente der Folge + \item z.B. multipliziere jedes Element mit 2 + \end{itemize*} + \item reduce(F, Seq) = wende eine Funktion F schrittweise auf die Elemente einer Folge Seq an und produziere einen einzelnen Wert, + \begin{itemize*} + \item prinzipiell ähnlich zu map(F, Seq), d.h. Funktion F kann auf Paare unabhängig angewendet werden + \item z.B. die Summe aller Elemente der Folge + \end{itemize*} \end{itemize*} \end{itemize*} -\end{itemize*} - -\paragraph{map in Erlang} - -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-erlang-map} -\end{center} - -\paragraph{reduce in Erlang} - -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-erlang-reduce} -\end{center} - -\paragraph{Parallelisierung von map und reduce} - -\color{orange} map \color{black} -\begin{itemize*} - \item Funktion F kann unabhängig (=parallel) auf jedes Element angewendet werden - \item Partitionieren und Verteilen der Elemente der Folge - \item siehe pmap -\end{itemize*} -\color{orange} reduce \color{black} -\begin{itemize*} - \item prinzipiell ähnlich, d.h. Funktion F kann auf Paare unabhängig angewandt werden -\end{itemize*} - -\paragraph{Parallelisierung von reduce} - -\begin{center} - \includegraphics[width=0.5\linewidth]{Assets/Programmierparadigmen-erlang-reduce-parallel} -\end{center} - -\subsubsection{Taskparallelität: Sortieren} -\color{orange} Quicksort in Erlang \color{black} -\begin{lstlisting}[language=erlang] + + \paragraph{map in Erlang} + + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-erlang-map} + \end{center} + + \paragraph{reduce in Erlang} + + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-erlang-reduce} + \end{center} + + \paragraph{Parallelisierung von map und reduce} + + \color{orange} map \color{black} + \begin{itemize*} + \item Funktion F kann unabhängig (=parallel) auf jedes Element angewendet werden + \item Partitionieren und Verteilen der Elemente der Folge + \item siehe pmap + \end{itemize*} + \color{orange} reduce \color{black} + \begin{itemize*} + \item prinzipiell ähnlich, d.h. Funktion F kann auf Paare unabhängig angewandt werden + \end{itemize*} + + \paragraph{Parallelisierung von reduce} + + \begin{center} + \includegraphics[width=0.5\linewidth]{Assets/Programmierparadigmen-erlang-reduce-parallel} + \end{center} + + \subsubsection{Taskparallelität: Sortieren} + \color{orange} Quicksort in Erlang \color{black} + \begin{lstlisting}[language=erlang] qsort([]) -> []; qsort([H|T]) -> qsort([Y || Y <- T, Y < H]) ++ [H] ++ qsort([Y || Y <- T, Y >= H]). \end{lstlisting} - -\begin{itemize*} - \item typische funktionale Notation von Quicksort mit List Comprehensions - \item Zeile 2: H dient als Pivotelement -\end{itemize*} -Idee: -\begin{itemize*} - \item Prozess für das Sortieren der einen Hälfte starten - \item Elternprozess kann andere Hälfte sortieren - \item rekursive Zerlegung... -\end{itemize*} - -\subsubsection{Parallel Quicksort} -\paragraph{Version 1} - -Quicksort in Erlang -\begin{lstlisting}[language=erlang] + + \begin{itemize*} + \item typische funktionale Notation von Quicksort mit List Comprehensions + \item Zeile 2: H dient als Pivotelement + \end{itemize*} + Idee: + \begin{itemize*} + \item Prozess für das Sortieren der einen Hälfte starten + \item Elternprozess kann andere Hälfte sortieren + \item rekursive Zerlegung... + \end{itemize*} + + \subsubsection{Parallel Quicksort} + \paragraph{Version 1} + + Quicksort in Erlang + \begin{lstlisting}[language=erlang] qsort2([]) -> []; qsort2([H|T]) -> Parent = self(), @@ -3593,15 +3596,15 @@ qsort2([H|T]) -> [H] ++ receive T2 -> T2 end. \end{lstlisting} - -Erläuterungen -\begin{itemize*} - \item Zeile 4: Erzeugen eines neuen Prozesses zur Sortierung der 'oberen' Hälfte - \item Zeile 6-7: Wie bisher - \item Zeile 8: Warten auf Empfang der sortierten anderen Hälfte -\end{itemize*} -Zeitmessung: -\begin{lstlisting} + + Erläuterungen + \begin{itemize*} + \item Zeile 4: Erzeugen eines neuen Prozesses zur Sortierung der 'oberen' Hälfte + \item Zeile 6-7: Wie bisher + \item Zeile 8: Warten auf Empfang der sortierten anderen Hälfte + \end{itemize*} + Zeitmessung: + \begin{lstlisting} > L = ch4_6:rand_list(100000). ... > ch4_6:benchmark(ch4_10, qsort, L). @@ -3609,26 +3612,26 @@ Zeitmessung: > ch4_6:benchmark(ch4_10, qsort2, L). 293.59211 \end{lstlisting} - -Bewertung -\begin{itemize*} - \item parallele Version 1 ist langsamer! - \item mögliche Erklärung: Prozess-Start ist aufwändiger als Sortieren kleiner Teilfolgen - \item bessere Variante nach John Hughes: Parallel Programming in Erlang + + Bewertung \begin{itemize*} - \item Kontrolle der Granularität für parallele Ausführungen - \item danach Sortieren mit sequenzieller Variante - \item einfache Idee: Anzahl der parallelen Zerlegung begrenzen + \item parallele Version 1 ist langsamer! + \item mögliche Erklärung: Prozess-Start ist aufwändiger als Sortieren kleiner Teilfolgen + \item bessere Variante nach John Hughes: Parallel Programming in Erlang + \begin{itemize*} + \item Kontrolle der Granularität für parallele Ausführungen + \item danach Sortieren mit sequenzieller Variante + \item einfache Idee: Anzahl der parallelen Zerlegung begrenzen + \end{itemize*} \end{itemize*} -\end{itemize*} -\begin{lstlisting}[language=erlang] + \begin{lstlisting}[language=erlang] qsort3(L) -> qsort3(4, L). % 4 Rekursionsstufen parallel qsort3(0, L) -> qsort(L); % Umschalten \end{lstlisting} - -\paragraph{Version 2} -\begin{lstlisting}[language=erlang] + + \paragraph{Version 2} + \begin{lstlisting}[language=erlang] qsort3(L) -> qsort3(6, L). qsort3(0, L) -> qsort(L); @@ -3641,29 +3644,29 @@ qsort3(N-1, [Y || Y <- T, Y < H]) ++ [H] ++ receive T2 -> T2 end. \end{lstlisting} -\begin{lstlisting} + \begin{lstlisting} > ch4_6:benchmark(ch4_10, qsort3, L). 87.54315 \end{lstlisting} - -\subsubsection{Fazit} -\begin{itemize*} - \item \color{orange} leichtgewichtige Prozesse \color{black} als Baustein der Parallelisierung in Erlang - \item Prozesskommunikation ausschließlich über \color{orange} Message Passing \color{black} - \item \color{orange} funktionaler Charakter \color{black} (u.a. Vermeidung von Seiteneffekten) vereinfacht Parallelisierung deutlich - \item \color{orange} Daten- und Taskparallelität \color{black} möglich - \item hoher Abstraktionsgrad, aber auch wenig Einflussmöglichkeiten -\end{itemize*} - -\subsection{Parallele Programmierung in C++} -\subsubsection{Threads in C++} -\color{orange} Thread (Faden) \color{black} = leichtgewichtige Ausführungseinheit oder Kontrollfluss (Folge von Anweisungen) innerhalb eines sich in Ausführung befindlichen Programms -\begin{itemize*} - \item Threads teilen sich den Adressraum ihres Prozesses - \item in C++: Instanzen der Klasse std::thread - \item führen eine (initiale) Funktion aus -\end{itemize*} -\begin{lstlisting}[language=C++] + + \subsubsection{Fazit} + \begin{itemize*} + \item \color{orange} leichtgewichtige Prozesse \color{black} als Baustein der Parallelisierung in Erlang + \item Prozesskommunikation ausschließlich über \color{orange} Message Passing \color{black} + \item \color{orange} funktionaler Charakter \color{black} (u.a. Vermeidung von Seiteneffekten) vereinfacht Parallelisierung deutlich + \item \color{orange} Daten- und Taskparallelität \color{black} möglich + \item hoher Abstraktionsgrad, aber auch wenig Einflussmöglichkeiten + \end{itemize*} + + \subsection{Parallele Programmierung in C++} + \subsubsection{Threads in C++} + \color{orange} Thread (Faden) \color{black} = leichtgewichtige Ausführungseinheit oder Kontrollfluss (Folge von Anweisungen) innerhalb eines sich in Ausführung befindlichen Programms + \begin{itemize*} + \item Threads teilen sich den Adressraum ihres Prozesses + \item in C++: Instanzen der Klasse std::thread + \item führen eine (initiale) Funktion aus + \end{itemize*} + \begin{lstlisting}[language=C++] #include #include @@ -3676,16 +3679,16 @@ qsort3(N-1, [Y || Y <- T, Y < H]) ++ t.join(); } \end{lstlisting} - -\paragraph{Alternative Erzeugung von Threads} - -\color{orange} über Lambda-Ausdruck \color{black} -\begin{lstlisting}[language=C++] + + \paragraph{Alternative Erzeugung von Threads} + + \color{orange} über Lambda-Ausdruck \color{black} + \begin{lstlisting}[language=C++] std::thread t([]() { do_something(); }); \end{lstlisting} - -\color{orange} mit Instanzen einer Klasse \color{black} - erfordert Überladen von operator() -\begin{lstlisting}[language=C++] + + \color{orange} mit Instanzen einer Klasse \color{black} - erfordert Überladen von operator() + \begin{lstlisting}[language=C++] struct my_task { void operator()() const { do_something(); } }; @@ -3694,14 +3697,14 @@ qsort3(N-1, [Y || Y <- T, Y < H]) ++ std::thread t1(tsk); // mit Objekt std::thread t2{ my_task() }; // über Konstruktor \end{lstlisting} - -\paragraph{Parameterübergabe bei Threaderzeugung} - -\begin{itemize*} - \item über zusätzliche Argumente des thread-Konstruktors - \item Vorsicht bei Übergabe von Referenzen, wenn Elternthread vor dem erzeugten Thread beendet wird -\end{itemize*} -\begin{lstlisting}[language=C++] + + \paragraph{Parameterübergabe bei Threaderzeugung} + + \begin{itemize*} + \item über zusätzliche Argumente des thread-Konstruktors + \item Vorsicht bei Übergabe von Referenzen, wenn Elternthread vor dem erzeugten Thread beendet wird + \end{itemize*} + \begin{lstlisting}[language=C++] void fun(int n, const std::string& s) { for (auto i = 0; i < n; i++) std::cout << s << " "; @@ -3710,20 +3713,20 @@ qsort3(N-1, [Y || Y <- T, Y < H]) ++ std::thread t(fun, 2, "Hello"); t.join(); \end{lstlisting} - -\paragraph{Warten auf Threads} - -\begin{itemize*} - \item t.join() wartet auf Beendigung des Threads t - \item blockiert aktuellen Thread - \item ohne join() keine Garantie, dass t zur Ausführung kommt - \item Freigabe der Ressourcen des Threads -\end{itemize*} -\begin{lstlisting}[language=C++] + + \paragraph{Warten auf Threads} + + \begin{itemize*} + \item t.join() wartet auf Beendigung des Threads t + \item blockiert aktuellen Thread + \item ohne join() keine Garantie, dass t zur Ausführung kommt + \item Freigabe der Ressourcen des Threads + \end{itemize*} + \begin{lstlisting}[language=C++] std::thread t([]() { do_something(); }); t.join(); \end{lstlisting} -\begin{lstlisting}[language=C++] + \begin{lstlisting}[language=C++] // Erscheint die Ausgabe? #include #include @@ -3736,22 +3739,22 @@ int main() { }); } \end{lstlisting} - -\paragraph{Hintergrundthreads} - -\begin{itemize*} - \item Threads können auch im Hintergrund laufen, ohne, dass auf Ende gewartet werden muss - \item 'abkoppeln' durch detach() - \item Thread läuft danach unter Kontrolle des C++-Laufzeitsystems, join nicht mehr möglich -\end{itemize*} - -\paragraph{Threadidentifikation} - -\begin{itemize*} - \item Threadidentifikator vom Typ std::thread::id - \item Ermittlung über Methode get\_id() -\end{itemize*} -\begin{lstlisting}[language=C++] + + \paragraph{Hintergrundthreads} + + \begin{itemize*} + \item Threads können auch im Hintergrund laufen, ohne, dass auf Ende gewartet werden muss + \item 'abkoppeln' durch detach() + \item Thread läuft danach unter Kontrolle des C++-Laufzeitsystems, join nicht mehr möglich + \end{itemize*} + + \paragraph{Threadidentifikation} + + \begin{itemize*} + \item Threadidentifikator vom Typ std::thread::id + \item Ermittlung über Methode get\_id() + \end{itemize*} + \begin{lstlisting}[language=C++] void fun() { std::cout << "Hello from " << std::this_thread::get_id() @@ -3760,12 +3763,12 @@ int main() { std::thread t(fun); t.join(); \end{lstlisting} - -\paragraph{Beispiel: Berechnung von Fibonacci-Zahlen in C++} -\begin{itemize*} - \item rekursive und nichtrekursive Variante möglich -\end{itemize*} -\begin{lstlisting}[language=C++] + + \paragraph{Beispiel: Berechnung von Fibonacci-Zahlen in C++} + \begin{itemize*} + \item rekursive und nichtrekursive Variante möglich + \end{itemize*} + \begin{lstlisting}[language=C++] unsigned int fibonacci(unsigned int n) { if(n == 0) return 0; @@ -3779,12 +3782,12 @@ unsigned int fibonacci(unsigned int n) { return f1; } \end{lstlisting} - -Parallele Berechnung von Fibonacci-Zahlen -\begin{itemize*} - \item einfachste Lösung (ähnlich zu Erlang): pro Zahl ein Thread -\end{itemize*} -\begin{lstlisting}[language=C++] + + Parallele Berechnung von Fibonacci-Zahlen + \begin{itemize*} + \item einfachste Lösung (ähnlich zu Erlang): pro Zahl ein Thread + \end{itemize*} + \begin{lstlisting}[language=C++] std::vector threads; unsigned int results[20]; @@ -3796,27 +3799,27 @@ for (auto i = 0u; i<20; i++){ } std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join)); \end{lstlisting} - -Erläuterungen -\begin{itemize*} - \item Zeile 1: Feld der Threads - \item Zeile 2: Feld für Ergebniswerte - \item Zeile 5: Zufallszahl erzeugen - \item Zeilen 6-7 Thread zur Berechnung der Fibonacci-Zahl erzeugen und Ergebnis im Feld speichern - \item Zeile 10-11: Warten auf Beendigung der Threads (std::mem\_fn = Wrapper für Zeiger auf Member-Funktion) - \item \color{orange} aber: \color{black} + + Erläuterungen \begin{itemize*} - \item Zugriff auf gemeinsame Ressource (results)! - \item Anzahl Fibonaccizahlen = Anzahl Threads + \item Zeile 1: Feld der Threads + \item Zeile 2: Feld für Ergebniswerte + \item Zeile 5: Zufallszahl erzeugen + \item Zeilen 6-7 Thread zur Berechnung der Fibonacci-Zahl erzeugen und Ergebnis im Feld speichern + \item Zeile 10-11: Warten auf Beendigung der Threads (std::mem\_fn = Wrapper für Zeiger auf Member-Funktion) + \item \color{orange} aber: \color{black} + \begin{itemize*} + \item Zugriff auf gemeinsame Ressource (results)! + \item Anzahl Fibonaccizahlen = Anzahl Threads + \end{itemize*} \end{itemize*} -\end{itemize*} - -\paragraph{parallel-for in C++} - -\begin{itemize*} - \item Unterstützung durch Higher-Level-APIs und Frameworks -\end{itemize*} -\begin{lstlisting}[language=C++] + + \paragraph{parallel-for in C++} + + \begin{itemize*} + \item Unterstützung durch Higher-Level-APIs und Frameworks + \end{itemize*} + \begin{lstlisting}[language=C++] // OpenMP #pragma omp parallel for for(auto i=0u; i(0, vec.size()), [&](tbb::blocked_range r){...}); \end{lstlisting} - -\paragraph{Kontrolle der Anzahl der Threads} - -\begin{itemize*} - \item Erzeugung von Threads ist mit Kosten verbunden - \item begrenzte Anzahl von Hardwarethreads (Anzahl Cores, Hyperthreading) - \item Ermittlung der Anzahl der unterstützten Hardwarethreads -\end{itemize*} -\begin{lstlisting}[language=C++] + + \paragraph{Kontrolle der Anzahl der Threads} + + \begin{itemize*} + \item Erzeugung von Threads ist mit Kosten verbunden + \item begrenzte Anzahl von Hardwarethreads (Anzahl Cores, Hyperthreading) + \item Ermittlung der Anzahl der unterstützten Hardwarethreads + \end{itemize*} + \begin{lstlisting}[language=C++] std::thread::hardware_concurrency() \end{lstlisting} -\begin{itemize*} - \item Nutzung für Implementierung von Threadpools, Task Libraries, ... -\end{itemize*} - -\subsubsection{Probleme nebenläufiger Ausführung} -\begin{lstlisting}[language=C++] + \begin{itemize*} + \item Nutzung für Implementierung von Threadpools, Task Libraries, ... + \end{itemize*} + + \subsubsection{Probleme nebenläufiger Ausführung} + \begin{lstlisting}[language=C++] struct jawsmith { std::string msg; @@ -3858,83 +3861,83 @@ struct jawsmith { } } \end{lstlisting} -\begin{lstlisting}[language=C++] + \begin{lstlisting}[language=C++] std::thread t1 { jawsmith("DASISTEINELANGENACHRICHT)}; std::thread t2 { jawsmith("dieistaberauchnichtkurz)}; \end{lstlisting} -Ausgabe: -\begin{lstlisting}[language=C++] + Ausgabe: + \begin{lstlisting}[language=C++] dDieistaberauchnichtkASISTEINELANGENACHurzRICHT... \end{lstlisting} - -\color{orange} Race Conditions \color{black}(Wettlaufsituationen) := Ergebnis nebenläufiger Ausführung auf gemeinsamen Zustand (hier: Ausgabekanal) hängt vom zeitlichen Verhalten der Einzeloperationen ab - - -\begin{itemize*} - \item Race Conditions (Wettlaufsituation) := Ergebnis nebenläufiger Ausführung auf gemeinsamen Zustand (hier: Ausgabekanal) hängt vom zeitlichen Verhalten der Einzeloperationen ab - \item kritischer Abschnitt: Programmabschnitt in einem Thread, in dem auf eine gemeinsame Ressource (Speicher etc.) zugegriffen wird und der nicht parallel (oder zeitlich verzahnt) zu einem anderen Thread ausgeführt werden darf - \item Lösung durch wechselseitigen Ausschluss (engl. mutual exclusion = mutex) + + \color{orange} Race Conditions \color{black}(Wettlaufsituationen) := Ergebnis nebenläufiger Ausführung auf gemeinsamen Zustand (hier: Ausgabekanal) hängt vom zeitlichen Verhalten der Einzeloperationen ab + + + \begin{itemize*} + \item Race Conditions (Wettlaufsituation) := Ergebnis nebenläufiger Ausführung auf gemeinsamen Zustand (hier: Ausgabekanal) hängt vom zeitlichen Verhalten der Einzeloperationen ab + \item kritischer Abschnitt: Programmabschnitt in einem Thread, in dem auf eine gemeinsame Ressource (Speicher etc.) zugegriffen wird und der nicht parallel (oder zeitlich verzahnt) zu einem anderen Thread ausgeführt werden darf + \item Lösung durch wechselseitigen Ausschluss (engl. mutual exclusion = mutex) + \begin{itemize*} + \item Instanz der Klasse std::mutex + \item Methoden zum Sperren ( lock ) und Freigeben ( unlock ) + \item 'mutex' : Standard-Mutex für exklusiven Zugriff + \item 'timed\_mutex' : Mutex mit Timeout für Warten ( try\_lock\_for() ) + \item 'recursive\_mutex' : rekursives Mutex - erlaubt mehrfaches Sperren durch einen Thread, z.B. für rekursive Aufrufe + \item 'recursive\_timed\_mutex' : rekursives Mutex mit Timeout + \item 'shared\_mutex' : Mutex, das gemeinsamen Zugriff ( lock\_shared() ) mehrerer Threads oder exklusiven Zugriff ( lock() ) ermöglicht + \item 'shared\_timed\_mutex' : Mutex mit Timeout und gemeinsamen Zugriff + \end{itemize*} + \item Lock Guards + \begin{itemize*} + \item Vereinfachung der Nutzung von Mutexen durch RAII ('Ressourcenbelegung ist Initialisierung') + \item Konstruktor = lock + \item Destruktor = unlock + \item std::unique\_lock erweiterte Variante von lock\_guard, vermeidet aber sofortiges Sperren + \item std::lock : erlaubt gleichzeitiges deadlock-freies Sperren von 2 Mutexen + \item Sperrstrategien: u.a. + \begin{itemize*} + \item std::try\_to\_lock versucht Sperre ohne Blockierung zu setzen + \item std::adopt\_lock versucht nicht, ein zweites Mal zu sperren, wenn bereits durch den aktuellen Thread gesperrt + \end{itemize*} + \end{itemize*} + \end{itemize*} + + \subsubsection{Wechselseitiger Ausschluss} + \begin{itemize*} + \item \color{orange} kritischer Abschnitt\color{black}: Programmabschnitt in einem Thread, in dem auf eine gemeinsame Ressource (Speicher etc.) zugegriffen wird und der nicht parallel (oder zeitlich verzahnt) zu einem anderen Thread ausgeführt werden darf + \item Lösung durch \color{orange} wechselseitigen Ausschluss \color{black} (engl. mutual exclusion = mutex) + \end{itemize*} + + \paragraph{Mutex in C++} + \begin{itemize*} \item Instanz der Klasse std::mutex - \item Methoden zum Sperren ( lock ) und Freigeben ( unlock ) - \item 'mutex' : Standard-Mutex für exklusiven Zugriff - \item 'timed\_mutex' : Mutex mit Timeout für Warten ( try\_lock\_for() ) - \item 'recursive\_mutex' : rekursives Mutex - erlaubt mehrfaches Sperren durch einen Thread, z.B. für rekursive Aufrufe - \item 'recursive\_timed\_mutex' : rekursives Mutex mit Timeout - \item 'shared\_mutex' : Mutex, das gemeinsamen Zugriff ( lock\_shared() ) mehrerer Threads oder exklusiven Zugriff ( lock() ) ermöglicht - \item 'shared\_timed\_mutex' : Mutex mit Timeout und gemeinsamen Zugriff + \item Methoden zum Sperren (lock) und Freigeben (unlock) \end{itemize*} - \item Lock Guards + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-mutex-c} + \end{center} + + \paragraph{Mutex-Varianten} + + \begin{itemize*} + \item mutex: Standard-Mutex für exklusiven Zugriff + \item timed\_mutex: Mutex mit Timeout für Warten (try\_lock\_for()) + \item recursive\_mutex:rekursives Mutex - erlaubt mehrfaches Sperren durch einen Thread, z.B. für rekursive Aufrufe + \item recursive\_timed\_mutex: rekursives Mutex mit Timeout + \item shared\_mutex: Mutex, das gemeinsamen Zugriff (lock\_shared()) mehrerer Threads oder exklusiven Zugriff (lock()) ermöglicht + \item shared\_timed\_mutex: Mutex mit Timeout und gemeinsamen Zugriff + \end{itemize*} + + \paragraph{Lock Guards} + \begin{itemize*} \item Vereinfachung der Nutzung von Mutexen durch RAII ('Ressourcenbelegung ist Initialisierung') \item Konstruktor = lock \item Destruktor = unlock - \item std::unique\_lock erweiterte Variante von lock\_guard, vermeidet aber sofortiges Sperren - \item std::lock : erlaubt gleichzeitiges deadlock-freies Sperren von 2 Mutexen - \item Sperrstrategien: u.a. - \begin{itemize*} - \item std::try\_to\_lock versucht Sperre ohne Blockierung zu setzen - \item std::adopt\_lock versucht nicht, ein zweites Mal zu sperren, wenn bereits durch den aktuellen Thread gesperrt - \end{itemize*} \end{itemize*} -\end{itemize*} - -\subsubsection{Wechselseitiger Ausschluss} -\begin{itemize*} - \item \color{orange} kritischer Abschnitt\color{black}: Programmabschnitt in einem Thread, in dem auf eine gemeinsame Ressource (Speicher etc.) zugegriffen wird und der nicht parallel (oder zeitlich verzahnt) zu einem anderen Thread ausgeführt werden darf - \item Lösung durch \color{orange} wechselseitigen Ausschluss \color{black} (engl. mutual exclusion = mutex) -\end{itemize*} - -\paragraph{Mutex in C++} - -\begin{itemize*} - \item Instanz der Klasse std::mutex - \item Methoden zum Sperren (lock) und Freigeben (unlock) -\end{itemize*} -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-mutex-c} -\end{center} - -\paragraph{Mutex-Varianten} - -\begin{itemize*} - \item mutex: Standard-Mutex für exklusiven Zugriff - \item timed\_mutex: Mutex mit Timeout für Warten (try\_lock\_for()) - \item recursive\_mutex:rekursives Mutex - erlaubt mehrfaches Sperren durch einen Thread, z.B. für rekursive Aufrufe - \item recursive\_timed\_mutex: rekursives Mutex mit Timeout - \item shared\_mutex: Mutex, das gemeinsamen Zugriff (lock\_shared()) mehrerer Threads oder exklusiven Zugriff (lock()) ermöglicht - \item shared\_timed\_mutex: Mutex mit Timeout und gemeinsamen Zugriff -\end{itemize*} - -\paragraph{Lock Guards} - -\begin{itemize*} - \item Vereinfachung der Nutzung von Mutexen durch RAII ('Ressourcenbelegung ist Initialisierung') - \item Konstruktor = lock - \item Destruktor = unlock -\end{itemize*} - -\begin{lstlisting}[language=C++] + + \begin{lstlisting}[language=C++] std::vector data; std::mutex my_mtx; @@ -3948,40 +3951,40 @@ dDieistaberauchnichtkASISTEINELANGENACHurzRICHT... return data.front(); } \end{lstlisting} - -\paragraph{Lock Gurads und Locks} - -\begin{itemize*} - \item std::unique\_lock erweiterte Variante von lock\_guards, vermeidet aber sofortiges Sperren - \item std::lock erlaubt gleichzeitiges deadlock-freies Sperren von 2 Mutexen - \item Sperrstrategien: u.a. + + \paragraph{Lock Gurads und Locks} + \begin{itemize*} - \item std::try\_to\_lock versucht Sperre ohne Blockierung zu setzen - \item std::adopt\_lock versucht nicht, ein zweites Mal zu sperren, wenn bereits durch den aktuellen Thread gesperrt + \item std::unique\_lock erweiterte Variante von lock\_guards, vermeidet aber sofortiges Sperren + \item std::lock erlaubt gleichzeitiges deadlock-freies Sperren von 2 Mutexen + \item Sperrstrategien: u.a. + \begin{itemize*} + \item std::try\_to\_lock versucht Sperre ohne Blockierung zu setzen + \item std::adopt\_lock versucht nicht, ein zweites Mal zu sperren, wenn bereits durch den aktuellen Thread gesperrt + \end{itemize*} \end{itemize*} -\end{itemize*} - -\paragraph{Atomare Datentypen} - -\begin{itemize*} - \item std::atomic\_flag = sperrfreier, atomarer Datentyp: + + \paragraph{Atomare Datentypen} + \begin{itemize*} - \item clear() setzt den Wert auf false - \item test\_and\_set() setzt den Wert atomar auf true und liefert den vorherigen Wert + \item std::atomic\_flag = sperrfreier, atomarer Datentyp: + \begin{itemize*} + \item clear() setzt den Wert auf false + \item test\_and\_set() setzt den Wert atomar auf true und liefert den vorherigen Wert + \end{itemize*} + \item std::atomic = mächtigere Variante, erlaubt explizites Setzen + \begin{itemize*} + \item operator= atomare Wertzuweisung + \item load() liefert den aktuellen Wert + \item read-modify-write-Operation (siehe später) + \end{itemize*} + \item std::atomic = generische Variante für weitere Datentypen \end{itemize*} - \item std::atomic = mächtigere Variante, erlaubt explizites Setzen - \begin{itemize*} - \item operator= atomare Wertzuweisung - \item load() liefert den aktuellen Wert - \item read-modify-write-Operation (siehe später) - \end{itemize*} - \item std::atomic = generische Variante für weitere Datentypen -\end{itemize*} - - -\paragraph{Synchronisation über atomare Variable} - -\begin{lstlisting}[language=C++] + + + \paragraph{Synchronisation über atomare Variable} + + \begin{lstlisting}[language=C++] std::list shared_space; std::atomic ready{false}; @@ -4001,62 +4004,62 @@ std::thread t1(consumer); std::thread t2(producer); ... \end{lstlisting} -Erläuterungen -\begin{itemize*} - \item Zeile 1: gemeinsam genutzte Liste - erfordert synchronisierten Zugriff - \item Zeile 2: atomare boolsche Variable ready - \item Zeile 4/12: Konsument/Produzent-Threads - \item Zeile 5: atomares prüfen der ready-Variablen - \item Zeile 6-7 kurz warten und neu versuchen - \item Zeile 8-9/13 Zugriff auf gemeinsame Liste - \item Zeile 14: atomares Setzen der Variablen ready -\end{itemize*} - -\subsubsection{Taskparallelität: Die 5 speisenden Philosophen} -\begin{itemize*} - \item fünf Philosophen teilen sich eine Schüssel Sphagetti - \item fünf Gabeln, je eine zwischen zwei Philosophen - \item Philosoph kann nur mit zwei benachbarten Gabeln essen - \item Gabeln werden nur nach dem Essen zurückgelegt - \item Philosoph durchläuft Zyklus von Zuständen: denken $\rightarrow$ hungrig $\rightarrow$ essen $\rightarrow$ denken $\rightarrow$ etc. -\end{itemize*} - -\paragraph{Das Problem mit den Philosophen} - -\begin{itemize*} - \item Jeder greift die linke Gabel - \item und wartet auf die rechte Gabel - \item ... und wartet ... -\end{itemize*} -\begin{center} - \includegraphics[width=0.3\linewidth]{Assets/Programmierparadigmen-philosophen} -\end{center} -\color{orange} \textbf{Verklemmung!} \color{black} - -\paragraph{Lösungsidee} - -\begin{itemize*} - \item immer beide Gabeln aufnehmen, dh. wenn nur eine Gabel verfügbar ist: liegen lassen und warten - \item synchronisierter Zugriff auf Gablen, dh. in einem kritischen Abschnitt unter gegenseitige Ausschluss - \item Wecken von wartenden Philosophen -\end{itemize*} - -\paragraph{Verklemmungsfreies Sperren} - -\begin{lstlisting}[language=C++] + Erläuterungen + \begin{itemize*} + \item Zeile 1: gemeinsam genutzte Liste - erfordert synchronisierten Zugriff + \item Zeile 2: atomare boolsche Variable ready + \item Zeile 4/12: Konsument/Produzent-Threads + \item Zeile 5: atomares prüfen der ready-Variablen + \item Zeile 6-7 kurz warten und neu versuchen + \item Zeile 8-9/13 Zugriff auf gemeinsame Liste + \item Zeile 14: atomares Setzen der Variablen ready + \end{itemize*} + + \subsubsection{Taskparallelität: Die 5 speisenden Philosophen} + \begin{itemize*} + \item fünf Philosophen teilen sich eine Schüssel Sphagetti + \item fünf Gabeln, je eine zwischen zwei Philosophen + \item Philosoph kann nur mit zwei benachbarten Gabeln essen + \item Gabeln werden nur nach dem Essen zurückgelegt + \item Philosoph durchläuft Zyklus von Zuständen: denken $\rightarrow$ hungrig $\rightarrow$ essen $\rightarrow$ denken $\rightarrow$ etc. + \end{itemize*} + + \paragraph{Das Problem mit den Philosophen} + + \begin{itemize*} + \item Jeder greift die linke Gabel + \item und wartet auf die rechte Gabel + \item ... und wartet ... + \end{itemize*} + \begin{center} + \includegraphics[width=0.3\linewidth]{Assets/Programmierparadigmen-philosophen} + \end{center} + \color{orange} \textbf{Verklemmung!} \color{black} + + \paragraph{Lösungsidee} + + \begin{itemize*} + \item immer beide Gabeln aufnehmen, dh. wenn nur eine Gabel verfügbar ist: liegen lassen und warten + \item synchronisierter Zugriff auf Gablen, dh. in einem kritischen Abschnitt unter gegenseitige Ausschluss + \item Wecken von wartenden Philosophen + \end{itemize*} + + \paragraph{Verklemmungsfreies Sperren} + + \begin{lstlisting}[language=C++] std::lock(mtx1, mtx2); std::lock_guard lk1(mtx1, std::adopt_lock); std::lock_guard lk2(mtx2, std::adopt_lock); \end{lstlisting} -Führt zu Verklemmung; Alternative Lösung -\begin{lstlisting}[language=C++] + Führt zu Verklemmung; Alternative Lösung + \begin{lstlisting}[language=C++] std::unique_lock lk1(mtx1, std::defer_lock); std::unique_lock lk2(mtx2, std::defer_lock); std::lock(lk1, lk2); \end{lstlisting} - -\paragraph{Gabeln und Spaghetti-Teller} -\begin{lstlisting}[language=C++] + + \paragraph{Gabeln und Spaghetti-Teller} + \begin{lstlisting}[language=C++] // Gabel = Mutex struct fork { std::mutex mtx; @@ -4069,9 +4072,9 @@ struct spaghetti_plate { std::array forks; } \end{lstlisting} - -\paragraph{Die Philosophen-Klasse} -\begin{lstlisting}[language=C++] + + \paragraph{Die Philosophen-Klasse} + \begin{lstlisting}[language=C++] class philosopher { private: int id; @@ -4087,26 +4090,26 @@ class philosopher { ... } \end{lstlisting} - -\paragraph{Die Philosophen-Klasse: Hilfsmethoden} - -Textausgabe erfordert synchronisierten Zugriff auf cout über globalen Mutex -\begin{lstlisting}[language=C++] + + \paragraph{Die Philosophen-Klasse: Hilfsmethoden} + + Textausgabe erfordert synchronisierten Zugriff auf cout über globalen Mutex + \begin{lstlisting}[language=C++] void say(const std::string& txt) { std::lock_guard lock(out_mtx); std::cout << "Philosopher #" << id << txt << std::endl; } \end{lstlisting} - -Hilfsmethode für zufällige Wartezeit in Millisekunden -\begin{lstlisting}[language=C++] + + Hilfsmethode für zufällige Wartezeit in Millisekunden + \begin{lstlisting}[language=C++] std::chrono::milliseconds wait() { return std::chrono::milliseconds(rand() % 500 + 100); } \end{lstlisting} - -\paragraph{Die Philosophen-Klasse: Essen} -\begin{lstlisting}[language=C++] + + \paragraph{Die Philosophen-Klasse: Essen} + \begin{lstlisting}[language=C++] void eating(){ //Versuche, die Gabeln (verklemmungsfrei) aufzunehmen std::lock(left_fork.mtx, right_fork.mtx); @@ -4121,22 +4124,22 @@ void eating(){ say(" finished eating."); } \end{lstlisting} - -\paragraph{Die Philosophen-Klasse: Denken} -\begin{lstlisting}[language=C++] + + \paragraph{Die Philosophen-Klasse: Denken} + \begin{lstlisting}[language=C++] void thinking() { say(" is thinking."); // Wenn Philosophen denken ... std::this_thread::sleep_for(wait()); } \end{lstlisting} - -\paragraph{Das Leben eines Philosophen} - -\begin{itemize*} - \item Zur Erinnerung: überladener ()-Operator eines Objekts definiert auszuführende Funktion eines Threads -\end{itemize*} -\begin{lstlisting}[language=C++] + + \paragraph{Das Leben eines Philosophen} + + \begin{itemize*} + \item Zur Erinnerung: überladener ()-Operator eines Objekts definiert auszuführende Funktion eines Threads + \end{itemize*} + \begin{lstlisting}[language=C++] void operator()(){ // Warten bis der Teller bereit ist while(!plate.ready); @@ -4147,9 +4150,9 @@ void operator()(){ } while (plate.ready); } \end{lstlisting} - -\paragraph{Das Dinner: Initialisierung} -\begin{lstlisting}[language=C++] + + \paragraph{Das Dinner: Initialisierung} + \begin{lstlisting}[language=C++] // der Teller spaghetti_plate plate; @@ -4166,14 +4169,14 @@ for (auto i=0u; i shared_space; std::mutex mtx; std::condition_variable cond; @@ -4245,9 +4248,9 @@ void consume(){ } } \end{lstlisting} - -\subsubsection{Thread-sichere Datenstrukturen} -\begin{lstlisting}[language=C++] + + \subsubsection{Thread-sichere Datenstrukturen} + \begin{lstlisting}[language=C++] void produce() { // data erzeugen std::lock_guard lg(mtx); @@ -4255,43 +4258,43 @@ void produce() { cond.notify_one(); } \end{lstlisting} -\begin{itemize*} - \item Thread-Sicherheit := eine Komponente kann gleichzeitig von verschiedenen Programmbereichen (Threads) mehrfach ausgeführt werden, ohne dass diese sich gegenseitig behindern - \item verschiedene Varianten: \begin{itemize*} - \item Standard-Datenstruktur + über Mutexe/Sperren synchronisierte Zugriffe - \item Integration der Sperren in die Datenstruktur - \item Sperr-freie Datenstrukturen: nicht-blockierend, Vermeidung von Sperren, z.B. durch Compare/Exchange-Operationen - \end{itemize*} - \item async , future und promise - \item std::future - Resultat einer asynchronen Berechnung, d.h. einer Berechnung die erst noch stattfindet - \item std::async() - asynchrones Starten eines Tasks - \begin{lstlisting}[language=C++] + \item Thread-Sicherheit := eine Komponente kann gleichzeitig von verschiedenen Programmbereichen (Threads) mehrfach ausgeführt werden, ohne dass diese sich gegenseitig behindern + \item verschiedene Varianten: + \begin{itemize*} + \item Standard-Datenstruktur + über Mutexe/Sperren synchronisierte Zugriffe + \item Integration der Sperren in die Datenstruktur + \item Sperr-freie Datenstrukturen: nicht-blockierend, Vermeidung von Sperren, z.B. durch Compare/Exchange-Operationen + \end{itemize*} + \item async , future und promise + \item std::future - Resultat einer asynchronen Berechnung, d.h. einer Berechnung die erst noch stattfindet + \item std::async() - asynchrones Starten eines Tasks + \begin{lstlisting}[language=C++] int long_calculation() { ... } std::future result = std::async(long_calculation); // Fortsetzung der Berechnung ... result.wait(); std::cout << result.get() << std::endl; \end{lstlisting} - \item std::promise - erlaubt Wert zu setzen, wenn der aktuelle Thread beendet ist; oft in Kombination mit std::future eingesetzt - \item future = Ergebnisobjekt, promise = Ergebnisproduzent - \begin{itemize*} - \item Warten auf Ende des Tasks (wait(), wait\_for()) - \item Ergebnis lesen (get()) + \item std::promise - erlaubt Wert zu setzen, wenn der aktuelle Thread beendet ist; oft in Kombination mit std::future eingesetzt + \item future = Ergebnisobjekt, promise = Ergebnisproduzent + \begin{itemize*} + \item Warten auf Ende des Tasks (wait(), wait\_for()) + \item Ergebnis lesen (get()) + \end{itemize*} \end{itemize*} -\end{itemize*} - -\subsubsection{Anforderungen} -\begin{itemize*} - \item mehrere Threads können gleichzeitig auf die Datenstruktur zugreifen - \item kein Thread sieht (Zwischen-)Zustand, bei dem Invarianten der Datenstruktur durch einen anderen Thread (kurzzeitig) verletzt ist - \item Vermeidung von Wettlaufsituationen - \item Vermeidung von Verklemmungen - \item korrekte Behandlung von Ausnahmen (Fehlern) -\end{itemize*} - -\subsubsection{Thread-sichere Queue} -\begin{lstlisting}[language=C++] + + \subsubsection{Anforderungen} + \begin{itemize*} + \item mehrere Threads können gleichzeitig auf die Datenstruktur zugreifen + \item kein Thread sieht (Zwischen-)Zustand, bei dem Invarianten der Datenstruktur durch einen anderen Thread (kurzzeitig) verletzt ist + \item Vermeidung von Wettlaufsituationen + \item Vermeidung von Verklemmungen + \item korrekte Behandlung von Ausnahmen (Fehlern) + \end{itemize*} + + \subsubsection{Thread-sichere Queue} + \begin{lstlisting}[language=C++] template class ts_queue { private: @@ -4303,28 +4306,28 @@ class ts_queue { ... }; \end{lstlisting} -\begin{itemize*} - \item Zeilen 1,2,6: Kapselung der std::queue-Klasse - \item Zeile 4: Mutex für exklusiven Zugriff - \item Zeile 5: Bedingungsvariable für Warten -\end{itemize*} - -\paragraph{Thread-sichere Queue: Methode push} -\begin{lstlisting}[language=C++] + \begin{itemize*} + \item Zeilen 1,2,6: Kapselung der std::queue-Klasse + \item Zeile 4: Mutex für exklusiven Zugriff + \item Zeile 5: Bedingungsvariable für Warten + \end{itemize*} + + \paragraph{Thread-sichere Queue: Methode push} + \begin{lstlisting}[language=C++] void push(T val){ std::lock_guard l(mtx); the_queue.push(std::move(val)); cond.notify_one(); } \end{lstlisting} -\begin{itemize*} - \item Zeile 2: Lock Guard sichert exklusiven Zugriff - \item Zeile 3: Element an die Queue anhängen - \item Zeile 4: Aufwecken von eventuell wartenden Threads -\end{itemize*} - -\paragraph{Thread-sichere Queue: Methode waiting\_pop} -\begin{lstlisting}[language=C++] + \begin{itemize*} + \item Zeile 2: Lock Guard sichert exklusiven Zugriff + \item Zeile 3: Element an die Queue anhängen + \item Zeile 4: Aufwecken von eventuell wartenden Threads + \end{itemize*} + + \paragraph{Thread-sichere Queue: Methode waiting\_pop} + \begin{lstlisting}[language=C++] void waiting_pop(T& val){ std::lock_guard l(mtx); cond.wait(l, [this] {return !the_queue.empty(); }); @@ -4332,42 +4335,42 @@ void waiting_pop(T& val){ the_queue.pop(); } \end{lstlisting} -\begin{itemize*} - \item Zeile 2: Lock Guard sichert exklusiven Zugriff - \item Zeile 3: Warten bis Queue nicht mehr leer ist - \item Zeilen 4,5: erstes Element aus der Queue entnehmen -\end{itemize*} - -\subsubsection{async, future und promise} -\begin{itemize*} - \item std::future - Resultat einer asynchronen Berechnung, d.h. einer Berechnung die erst noch stattfindet - \item std::async() - asynchrones Starten eines Tasks - \begin{lstlisting}[language=C++] + \begin{itemize*} + \item Zeile 2: Lock Guard sichert exklusiven Zugriff + \item Zeile 3: Warten bis Queue nicht mehr leer ist + \item Zeilen 4,5: erstes Element aus der Queue entnehmen + \end{itemize*} + + \subsubsection{async, future und promise} + \begin{itemize*} + \item std::future - Resultat einer asynchronen Berechnung, d.h. einer Berechnung die erst noch stattfindet + \item std::async() - asynchrones Starten eines Tasks + \begin{lstlisting}[language=C++] int long_calculation() {...} std::future result = std::async(long_calculation); //Fortsetzung der Berechnung ... result.wait(); std::cout << result.get() << std::endl; \end{lstlisting} - \item std::promise - erlaubt Wert zu setzen, wenn der aktuelle Thread beendet ist, of in Kombination mit std::future eingesetzt - \item future = Ergbenisobjekt, promise = Ergebnisproduzent -\end{itemize*} - -\paragraph{Future} - -\begin{itemize*} - \item Methoden zum - \begin{itemize*} - \item Warten auf das Ende des Tasks (wait(), wait\_for()) - \item Ergebnis lesen (get()) + \item std::promise - erlaubt Wert zu setzen, wenn der aktuelle Thread beendet ist, of in Kombination mit std::future eingesetzt + \item future = Ergbenisobjekt, promise = Ergebnisproduzent \end{itemize*} -\end{itemize*} -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-future-task} -\end{center} - -Beispiel -\begin{lstlisting}[language=C++] + + \paragraph{Future} + + \begin{itemize*} + \item Methoden zum + \begin{itemize*} + \item Warten auf das Ende des Tasks (wait(), wait\_for()) + \item Ergebnis lesen (get()) + \end{itemize*} + \end{itemize*} + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-future-task} + \end{center} + + Beispiel + \begin{lstlisting}[language=C++] // Promise für einen String Wert std::promise promise; //zugehöriges Future Objekt @@ -4384,39 +4387,39 @@ auto consumer = std::thread([&]{ producer.join(); consumer.join(); \end{lstlisting} - -\subsubsection{Deklarative Parallelisierung mit OpenMP} -\begin{itemize*} - \item Programmierschnittstelle für Parallelisierung in C/C++/Fortran - \item Programmiersprachenerweiterung durch Direktiven - \item in C/C++: \#pragma omp ... - \item zusätzliche Bibliotheksfunktionen: \#include - \item aktuelle Version 5.0 - \item Unterstützung in gcc und clang + + \subsubsection{Deklarative Parallelisierung mit OpenMP} \begin{itemize*} - \item vollständig 4.5, partiell 5.0 - \item Nutzung über Compilerflag - fopenmp + \item Programmierschnittstelle für Parallelisierung in C/C++/Fortran + \item Programmiersprachenerweiterung durch Direktiven + \item in C/C++: \#pragma omp ... + \item zusätzliche Bibliotheksfunktionen: \#include + \item aktuelle Version 5.0 + \item Unterstützung in gcc und clang + \begin{itemize*} + \item vollständig 4.5, partiell 5.0 + \item Nutzung über Compilerflag - fopenmp + \end{itemize*} + \item beschränkt auf Architekturen mit gemeinsamen Speicher \end{itemize*} - \item beschränkt auf Architekturen mit gemeinsamen Speicher -\end{itemize*} - -\subsubsection{Programmiermodell} -\begin{itemize*} - \item Master-Thread und mehrere Worker-Threads (Anzahl typischerweise durch OpenMP-Laufzeitsystem bestimmt) - \item über parallel-Direktive kann Arbeit in einem Programmabschnitt auf Worker-Threads aufgeteilt werden - \item Ende des parallelen Abschnitts $\rightarrowtail$ implizite Synchronisation - \item Fortsetzung des Master-Threads -\end{itemize*} -\begin{center} - \includegraphics[width=0.2\linewidth]{Assets/Programmierparadigmen-master-worker-thread} -\end{center} - -\begin{itemize*} - \item Master-Thread und mehrere Worker-Threads (Anzahl typischerweise durch OpenMP-Laufzeitsystem bestimmt) - \item über parallel -Direktive kann Arbeit in einem Programmabschnitt auf Worker-Threads aufgeteilt werden - \item Ende des parallelen Abschnitts $\rightarrow$ implizite Synchronisation $\rightarrow$ Fortsetzung des Master-Threads - \item der dem 'pragma' folgende Block wird parallel von allen Threads ausgeführt - \begin{lstlisting}[language=C++] + + \subsubsection{Programmiermodell} + \begin{itemize*} + \item Master-Thread und mehrere Worker-Threads (Anzahl typischerweise durch OpenMP-Laufzeitsystem bestimmt) + \item über parallel-Direktive kann Arbeit in einem Programmabschnitt auf Worker-Threads aufgeteilt werden + \item Ende des parallelen Abschnitts $\rightarrowtail$ implizite Synchronisation + \item Fortsetzung des Master-Threads + \end{itemize*} + \begin{center} + \includegraphics[width=0.2\linewidth]{Assets/Programmierparadigmen-master-worker-thread} + \end{center} + + \begin{itemize*} + \item Master-Thread und mehrere Worker-Threads (Anzahl typischerweise durch OpenMP-Laufzeitsystem bestimmt) + \item über parallel -Direktive kann Arbeit in einem Programmabschnitt auf Worker-Threads aufgeteilt werden + \item Ende des parallelen Abschnitts $\rightarrow$ implizite Synchronisation $\rightarrow$ Fortsetzung des Master-Threads + \item der dem 'pragma' folgende Block wird parallel von allen Threads ausgeführt + \begin{lstlisting}[language=C++] #include #include int main() { @@ -4430,52 +4433,52 @@ consumer.join(); return 0; } \end{lstlisting} - \item Schleifenparallelisierung: jedem Thread wird ein Teil der Iteration zugewiesen (beeinflusst nur äußere Schleife) - \begin{lstlisting}[language=C++] + \item Schleifenparallelisierung: jedem Thread wird ein Teil der Iteration zugewiesen (beeinflusst nur äußere Schleife) + \begin{lstlisting}[language=C++] ... #pragma omp parallel for for (int i = 0; i < 20; i++) {... \end{lstlisting} - \begin{itemize*} - \item collapse(n) gibt an, dass n Schleifen in einem gemeinsamen Iterationsbereich zusammengefasst und auf die Threads verteilt werden sollen - \begin{lstlisting} + \begin{itemize*} + \item collapse(n) gibt an, dass n Schleifen in einem gemeinsamen Iterationsbereich zusammengefasst und auf die Threads verteilt werden sollen + \begin{lstlisting} #pragma omp parallel for collapse(3) \end{lstlisting} - \end{itemize*} - \item Beeinflussung der Thread Anzahl - \begin{itemize*} - \item maximale Anzahl: - \begin{lstlisting} + \end{itemize*} + \item Beeinflussung der Thread Anzahl + \begin{itemize*} + \item maximale Anzahl: + \begin{lstlisting} #pragma omp parallel for num_threads(8) \end{lstlisting} - \item bedingte Parallelisierung: - \begin{lstlisting} + \item bedingte Parallelisierung: + \begin{lstlisting} #pragma omp parallel for if(i>50) \end{lstlisting} - \end{itemize*} - \item Aufteilung des Iterationsbereichs; Beeinflussung durch schedule -Direktive - \begin{description*} - \item[schedule(auto)] Default - implementierungsspezifisch - \item[schedule(static, n)] statische Round-Robin-Verteilung - Bereiche der Größe n (Angabe von n ist optional) - \item[schedule(dynamic, n)] dynamische Verteilung nach Bedarf - \item[schedule(guided, n)] Verteilung nach Bedarf und proportional zur Restarbeit - \end{description*} - \item Direktiven für parallele Ausführung - \begin{description*} - \item[\#pragma omp single/master] Abschnitt wird nur durch einen/den Master-Thread ausgeführt - \item[\#pragma omp critical] kritischer Abschnitt - \item[\#pragma omp barrier] Warten auf alle Worker-Threads - \item[\#pragma omp atomic] kritischer Abschnitt, Zugriff auf gemeinsame Variable (z.B. Zähler) - \end{description*} - \item Speicherklauseln für Variablen - \begin{itemize*} - \item 'shared' für alle Threads sichtbar/änderbar - \item 'private' jeder Thread hat eigene Kopie der Daten, wird nicht außerhalb initialisiert - \item 'reduction' private Daten, die am Ende des Abschnitts zu globalem Wert zusammengefasst werden - \item 'firstprivate / lastprivate' privat - initialisiert mit letztem Wert vor dem Abschnitt / Wert des letzten Threads der Iteration wird zurückgegeben - \end{itemize*} - \item zuweisung von Programmabschnitten zu Threads $\rightarrow$ statische Parallelität (geeignet für rekursive Abschnitte) - \begin{lstlisting}[language=C++] + \end{itemize*} + \item Aufteilung des Iterationsbereichs; Beeinflussung durch schedule -Direktive + \begin{description*} + \item[schedule(auto)] Default - implementierungsspezifisch + \item[schedule(static, n)] statische Round-Robin-Verteilung - Bereiche der Größe n (Angabe von n ist optional) + \item[schedule(dynamic, n)] dynamische Verteilung nach Bedarf + \item[schedule(guided, n)] Verteilung nach Bedarf und proportional zur Restarbeit + \end{description*} + \item Direktiven für parallele Ausführung + \begin{description*} + \item[\#pragma omp single/master] Abschnitt wird nur durch einen/den Master-Thread ausgeführt + \item[\#pragma omp critical] kritischer Abschnitt + \item[\#pragma omp barrier] Warten auf alle Worker-Threads + \item[\#pragma omp atomic] kritischer Abschnitt, Zugriff auf gemeinsame Variable (z.B. Zähler) + \end{description*} + \item Speicherklauseln für Variablen + \begin{itemize*} + \item 'shared' für alle Threads sichtbar/änderbar + \item 'private' jeder Thread hat eigene Kopie der Daten, wird nicht außerhalb initialisiert + \item 'reduction' private Daten, die am Ende des Abschnitts zu globalem Wert zusammengefasst werden + \item 'firstprivate / lastprivate' privat - initialisiert mit letztem Wert vor dem Abschnitt / Wert des letzten Threads der Iteration wird zurückgegeben + \end{itemize*} + \item zuweisung von Programmabschnitten zu Threads $\rightarrow$ statische Parallelität (geeignet für rekursive Abschnitte) + \begin{lstlisting}[language=C++] #pragma omp parallel sections { #pragma omp section @@ -4484,13 +4487,13 @@ consumer.join(); qsort(data, p + 1, right); } \end{lstlisting} - \item Task Programmierung - \begin{itemize*} - \item reihum Threads zugewiesen werden - \item an beliebiger Stelle definiert werden können - \item von beliebigem Thread definiert werden kann - \end{itemize*} - \begin{lstlisting}[language=C++] + \item Task Programmierung + \begin{itemize*} + \item reihum Threads zugewiesen werden + \item an beliebiger Stelle definiert werden können + \item von beliebigem Thread definiert werden kann + \end{itemize*} + \begin{lstlisting}[language=C++] unsigned int f1, f2; #pragma omp task shared(f1) f1 = fib(f - 1); @@ -4499,13 +4502,13 @@ consumer.join(); #pragma omp taskwait return f1 + f2; \end{lstlisting} -\end{itemize*} - -\subsubsection{Hello World! mit OpenMP} -\begin{itemize*} - \item der dem pragma folgende Block wird parallel von allen Threads ausgeführt -\end{itemize*} -\begin{lstlisting}[language=C++] + \end{itemize*} + + \subsubsection{Hello World! mit OpenMP} + \begin{itemize*} + \item der dem pragma folgende Block wird parallel von allen Threads ausgeführt + \end{itemize*} + \begin{lstlisting}[language=C++] #include #include @@ -4520,13 +4523,13 @@ int main() { return 0; } \end{lstlisting} - -\subsubsection{Schleifenparallelisierung} -\begin{itemize*} - \item parallele Ausführung einer Schleife: jedem Thread wird ein Teil der Iterationen zugewiesen - \item für for-Schleifgen mit eingeschränkter Syntax (ganzzahlige Schleifenvariablen, Operatoren auf Schleifenvariablen) und für STL-Iteratoren -\end{itemize*} -\begin{lstlisting}[language=C++] + + \subsubsection{Schleifenparallelisierung} + \begin{itemize*} + \item parallele Ausführung einer Schleife: jedem Thread wird ein Teil der Iterationen zugewiesen + \item für for-Schleifgen mit eingeschränkter Syntax (ganzzahlige Schleifenvariablen, Operatoren auf Schleifenvariablen) und für STL-Iteratoren + \end{itemize*} + \begin{lstlisting}[language=C++] unsigned int results[20]; #pragma omp parallel for for (int i = 0; i < 20; i++){ @@ -4534,77 +4537,77 @@ for (int i = 0; i < 20; i++){ results[i] = fibonacci(f); } \end{lstlisting} - -\subsubsection{Beeinflussung der Thread-Anzahl} -maximale Anzahl -\begin{lstlisting}[language=C++] + + \subsubsection{Beeinflussung der Thread-Anzahl} + maximale Anzahl + \begin{lstlisting}[language=C++] unsigned int results[20]; #pragma omp parallel for num_threads(8) for (int i = 0; i < 20; i++){ results[i] = fibonacci(rand() % 30); } \end{lstlisting} - -bedingte Parallelisierung -\begin{lstlisting}[language=C++] + + bedingte Parallelisierung + \begin{lstlisting}[language=C++] unsigned int results[20]; #pragma omp parallel for if(i > 50) for (int i = 0; i < 20; i++){ results[i] = fibonacci(rand() % 30); } \end{lstlisting} - -\subsubsection{Aufteilung des Iterationsbereichs} -\begin{itemize*} - \item Iterationsbereich kann auf verschiedene Weise auf Threads aufgeteilt werden - \item Beeinflussung durch schedule-Direktive - \begin{description*} - \item[schedule(auto)] Default - implementierungsspezifisch - \item[schedule(static,n)] statische Round-Robin-Verteilung - Bereiche der Größe n (Angabe von n ist optional) - \item[schedule(dynamic, n)] dynamische Verteilung nach Bedarf - \item[schedule(guided, n)] Verteilung nach Bedarf und proportional zur Restarbeit - \item[...] - \end{description*} -\end{itemize*} - -\subsubsection{Geschachtelte Schleifen} -\begin{itemize*} - \item Parallelisierung mit \textbf{parallel for} beeinflusst nur äußere Schleife - \item collapse(n) gibt an, dass n Schleifen in einem gemeinsamen Iterationsbereich zusammengefasst, und auf die Threads verteilt werden sollen - \item Beispiel: Matrizenmultiplikation - \begin{lstlisting}[language=C++] + + \subsubsection{Aufteilung des Iterationsbereichs} + \begin{itemize*} + \item Iterationsbereich kann auf verschiedene Weise auf Threads aufgeteilt werden + \item Beeinflussung durch schedule-Direktive + \begin{description*} + \item[schedule(auto)] Default - implementierungsspezifisch + \item[schedule(static,n)] statische Round-Robin-Verteilung - Bereiche der Größe n (Angabe von n ist optional) + \item[schedule(dynamic, n)] dynamische Verteilung nach Bedarf + \item[schedule(guided, n)] Verteilung nach Bedarf und proportional zur Restarbeit + \item[...] + \end{description*} + \end{itemize*} + + \subsubsection{Geschachtelte Schleifen} + \begin{itemize*} + \item Parallelisierung mit \textbf{parallel for} beeinflusst nur äußere Schleife + \item collapse(n) gibt an, dass n Schleifen in einem gemeinsamen Iterationsbereich zusammengefasst, und auf die Threads verteilt werden sollen + \item Beispiel: Matrizenmultiplikation + \begin{lstlisting}[language=C++] #pragma omp parallel for collapse(3) for (int row = 0; row < m; row++) for(int col = 0; col < n; col++) for(int inner = 0; inner < k; inner++) prod[row][col] += A[row][inner] * B[inner][col]; \end{lstlisting} -\end{itemize*} - -\subsubsection{Synchronisation} -\begin{itemize*} - \item Direktiven für parallele Ausführung - \begin{description*} - \item[\#pragma omp single/master] Abschnitt wird nur durch einen/den Master-Thread ausgeführt - \item[\#pragma omp critical] kritischer Abschnitt - \item[\#pragma omp barrier] Warten auf alle Worker-Threads - \item[\#pragma omp atomic] kritischer Abschnitt - ZUgriff auf gemeinsame Variable (z.B. Zähler) - \end{description*} - \item Speicherklauseln für Variablen - \begin{description*} - \item[shared] für alle Threads sichtbar/änderbar - \item[private] jeder Thread hat eigene Kopie der Daten, wird nicht außerhalb initialisiert - \item[reduction] private Daten, die am Ende des Abschnitts zu globalem Wert zusammengefasst werden - \item[firstprivate/lastprivate] privat - initialisiert mit letztem Wert vor dem Abschnitt / Wert des letzten Threads der Iteration wird zurückgegeben - \end{description*} -\end{itemize*} - -\subsubsection{Parallele Abschnitte} -\begin{itemize*} - \item Zuweisung von Programmabschnitten zu Threads $\rightarrowtail$ statische Parallelität - \item geeignet z.B. für rekursive Aufrufe -\end{itemize*} -\begin{lstlisting}[language=C++] + \end{itemize*} + + \subsubsection{Synchronisation} + \begin{itemize*} + \item Direktiven für parallele Ausführung + \begin{description*} + \item[\#pragma omp single/master] Abschnitt wird nur durch einen/den Master-Thread ausgeführt + \item[\#pragma omp critical] kritischer Abschnitt + \item[\#pragma omp barrier] Warten auf alle Worker-Threads + \item[\#pragma omp atomic] kritischer Abschnitt - ZUgriff auf gemeinsame Variable (z.B. Zähler) + \end{description*} + \item Speicherklauseln für Variablen + \begin{description*} + \item[shared] für alle Threads sichtbar/änderbar + \item[private] jeder Thread hat eigene Kopie der Daten, wird nicht außerhalb initialisiert + \item[reduction] private Daten, die am Ende des Abschnitts zu globalem Wert zusammengefasst werden + \item[firstprivate/lastprivate] privat - initialisiert mit letztem Wert vor dem Abschnitt / Wert des letzten Threads der Iteration wird zurückgegeben + \end{description*} + \end{itemize*} + + \subsubsection{Parallele Abschnitte} + \begin{itemize*} + \item Zuweisung von Programmabschnitten zu Threads $\rightarrowtail$ statische Parallelität + \item geeignet z.B. für rekursive Aufrufe + \end{itemize*} + \begin{lstlisting}[language=C++] void qsort(int data[], int left, int right) { if( left < right) { int p = partition(data, left, right); @@ -4618,17 +4621,17 @@ void qsort(int data[], int left, int right) { } } \end{lstlisting} - -\subsubsection{Task-Programmierung mit OpenMP} -\begin{itemize*} - \item seit OpenMP 3.0 Unterstützung von Tasks, die + + \subsubsection{Task-Programmierung mit OpenMP} \begin{itemize*} - \item reihum Threads zugewiesen werden - \item an beliebiger Stelle definiert werden können - \item von beliebigem Thread definiert werden kann + \item seit OpenMP 3.0 Unterstützung von Tasks, die + \begin{itemize*} + \item reihum Threads zugewiesen werden + \item an beliebiger Stelle definiert werden können + \item von beliebigem Thread definiert werden kann + \end{itemize*} \end{itemize*} -\end{itemize*} -\begin{lstlisting}[language=C++] + \begin{lstlisting}[language=C++] unsigned int fibonacci(unsigned int f){ if( f<2 ) return n; unsigned int f1, f2; @@ -4640,21 +4643,21 @@ unsigned int fibonacci(unsigned int f){ return f1 + f2; } \end{lstlisting} - -\subsubsection{Fazit} -\begin{itemize*} - \item C++ bietet weitreichende und mächtige Konzepte zur Parallelisierung + + \subsubsection{Fazit} \begin{itemize*} - \item von Basiskontrolle wie Threads und Synchronisationsprimitiven (u.a. Mutexe) - \item ...über höherwertige Abstraktionen wie async, Features und Promises - \item bis hin zu deklarativen Ansätzen wie OpenMP + \item C++ bietet weitreichende und mächtige Konzepte zur Parallelisierung + \begin{itemize*} + \item von Basiskontrolle wie Threads und Synchronisationsprimitiven (u.a. Mutexe) + \item ...über höherwertige Abstraktionen wie async, Features und Promises + \item bis hin zu deklarativen Ansätzen wie OpenMP + \end{itemize*} + \item alle Formen von Parallelität (Instruktions-, Daten-, und Taskparallelität) möglich + \item aber anspruchsvolle Programmierung + \item erleichtert durch zusätzliche Bibliotheken und Frameworks wie Parallel STL, TBB, ... \end{itemize*} - \item alle Formen von Parallelität (Instruktions-, Daten-, und Taskparallelität) möglich - \item aber anspruchsvolle Programmierung - \item erleichtert durch zusätzliche Bibliotheken und Frameworks wie Parallel STL, TBB, ... -\end{itemize*} - -\begin{lstlisting}[language=java] + + \begin{lstlisting}[language=java] //[Hello.java] package runnable; public class Hello { @@ -4677,8 +4680,8 @@ public class Hello { } } \end{lstlisting} -\hfill -\begin{lstlisting}[language=C++] + \hfill + \begin{lstlisting}[language=C++] //[Hello.cpp] #include // Datei iostream aus System-Includes #include "X.hpp" // Datei X.hpp aus Projekt-Ordner @@ -4712,8 +4715,8 @@ int main(int argc, char* argv[]){ return 0; } \end{lstlisting} -\hfill -\begin{lstlisting}[language=erlang] + \hfill + \begin{lstlisting}[language=erlang] %[Hello.erl] -module(cheat_sheet). % end with a period @@ -4746,42 +4749,42 @@ countdown(Start) -> countdown() -> countdown(10). \end{lstlisting} - - -\subsection{Parallele Programmierung in Java} -Unterstützung durch -\begin{itemize*} - \item Thread-Konzept - \item eingebaute Mechanismen zur Synchronisation nebenläufiger Prozesse - \item spezielle High-Level-Klassen im Package - \newline \textbf{java.util.concurrent} -\end{itemize*} - -\subsubsection{Threads in Java} -\begin{itemize*} - \item Repräsentiert durch Klasse \textbf{java.lang.Thread} - \item Implementierung eines eigenen Kontrollflusses - \item Eigene Klasse muss Runnable implementieren + + + \subsection{Parallele Programmierung in Java} + Unterstützung durch \begin{itemize*} - \item Implementierung des Interface \textbf{java.lang.Runnable} + \item Thread-Konzept + \item eingebaute Mechanismen zur Synchronisation nebenläufiger Prozesse + \item spezielle High-Level-Klassen im Package + \newline \textbf{java.util.concurrent} + \end{itemize*} + + \subsubsection{Threads in Java} + \begin{itemize*} + \item Repräsentiert durch Klasse \textbf{java.lang.Thread} + \item Implementierung eines eigenen Kontrollflusses + \item Eigene Klasse muss Runnable implementieren \begin{itemize*} - \item keine weitere Beeinflussung des Threads über zusätzliche Methoden notwendig - \item soll von anderer Klasse als Thread abgeleitet werden - \end{itemize*} - \item Subklasse von \textbf{java.lang.Thread} - \begin{itemize*} - \item zusätzliche Methoden zur Steuerung des Ablaufs benötigt - \item keine andere Superklasse notwendig + \item Implementierung des Interface \textbf{java.lang.Runnable} + \begin{itemize*} + \item keine weitere Beeinflussung des Threads über zusätzliche Methoden notwendig + \item soll von anderer Klasse als Thread abgeleitet werden + \end{itemize*} + \item Subklasse von \textbf{java.lang.Thread} + \begin{itemize*} + \item zusätzliche Methoden zur Steuerung des Ablaufs benötigt + \item keine andere Superklasse notwendig + \end{itemize*} \end{itemize*} \end{itemize*} -\end{itemize*} - -\paragraph{Threads: Runnable-Schnittstelle} -Eigene Klasse muss \textbf{Runnable} implementieren -\begin{itemize*} - \item Methode \textbf{public void run()} - wird beim Start des Threads aufgerufen -\end{itemize*} -\begin{lstlisting}[language=java] + + \paragraph{Threads: Runnable-Schnittstelle} + Eigene Klasse muss \textbf{Runnable} implementieren + \begin{itemize*} + \item Methode \textbf{public void run()} - wird beim Start des Threads aufgerufen + \end{itemize*} + \begin{lstlisting}[language=java] public class Heartbeat implements Runnable { int pulse; public Heartbeat(int p) { pulse = p * 1000; } @@ -4794,30 +4797,30 @@ public class Heartbeat implements Runnable { } } \end{lstlisting} - -\paragraph{Thread-Erzeugung} - -\begin{itemize*} - \item Thread-Objekt mit Runnable-Objekt erzeugen - \item Methode \textbf{start()} aufrufen + + \paragraph{Thread-Erzeugung} + \begin{itemize*} - \item Ruft \textbf{run()} auf + \item Thread-Objekt mit Runnable-Objekt erzeugen + \item Methode \textbf{start()} aufrufen + \begin{itemize*} + \item Ruft \textbf{run()} auf + \end{itemize*} \end{itemize*} -\end{itemize*} -\begin{lstlisting}[language=java] + \begin{lstlisting}[language=java] public static void main(String[] args) { Thread t = new Thread(new Heartbeat(2)); //Thread Objekt mit runnable erzeugen t.start(); //methode start() aufrufen -> ruft run() auf } \end{lstlisting} - -\paragraph{Subklasse von Thread} - -\begin{itemize*} - \item Klasse muss von Thread abgeleitet werden - \item Methode run() muss überschrieben werden -\end{itemize*} -\begin{lstlisting}[language=java] + + \paragraph{Subklasse von Thread} + + \begin{itemize*} + \item Klasse muss von Thread abgeleitet werden + \item Methode run() muss überschrieben werden + \end{itemize*} + \begin{lstlisting}[language=java] public class Heartbeat2 implements Runnable { int pulse = 1000; public Heartbeat2() {} @@ -4832,38 +4835,38 @@ public class Heartbeat2 implements Runnable { } } \end{lstlisting} - -\begin{itemize*} - \item Objekt der eigenen Thread-Klasse erzeugen - \item Methode \textbf{start()} aufrufen + \begin{itemize*} - \item Ruft \textbf{run()} auf - \end{itemize*} - \begin{lstlisting}[language=java] + \item Objekt der eigenen Thread-Klasse erzeugen + \item Methode \textbf{start()} aufrufen + \begin{itemize*} + \item Ruft \textbf{run()} auf + \end{itemize*} + \begin{lstlisting}[language=java] public static voud main(String[] args) { Heartbeat2 t = new Heartbeat2(2); t.start(); } \end{lstlisting} - \item Spätere Beeinflussung durch andere Threads möglich - \begin{lstlisting}[language=java] + \item Spätere Beeinflussung durch andere Threads möglich + \begin{lstlisting}[language=java] ... t.setPulse(2); \end{lstlisting} -\end{itemize*} - - -\paragraph{Threads: Wichtige Methoden} - -\begin{description*} - \item[void start()] initiiert Ausführung des Threads durch Aufruf der Methode run - \item[void run()] die eigentliche Arbeitsmethode - \item[static void sleep(int millis)] hält die Ausführung des aktuellen Threads für 'millis' Millisekunden an; Keinen Einfluss auf andere Threads! - \item[void join()] blockiert den aufrufenden Thread so lange, bis der aufgerufene Thread beendet ist -\end{description*} - -\subsubsection{Parallele Berechnung von Fibonacci-Zahlen} -\begin{lstlisting}[language=java] + \end{itemize*} + + + \paragraph{Threads: Wichtige Methoden} + + \begin{description*} + \item[void start()] initiiert Ausführung des Threads durch Aufruf der Methode run + \item[void run()] die eigentliche Arbeitsmethode + \item[static void sleep(int millis)] hält die Ausführung des aktuellen Threads für 'millis' Millisekunden an; Keinen Einfluss auf andere Threads! + \item[void join()] blockiert den aufrufenden Thread so lange, bis der aufgerufene Thread beendet ist + \end{description*} + + \subsubsection{Parallele Berechnung von Fibonacci-Zahlen} + \begin{lstlisting}[language=java] public class Fibonacci implements Runnable { int fi; public Fibonacci(int f) {fi = f; } @@ -4877,9 +4880,9 @@ public class Fibonacci implements Runnable { } } \end{lstlisting} - -Thread-Erzeugung und Ausführung -\begin{lstlisting}[language=java] + + Thread-Erzeugung und Ausführung + \begin{lstlisting}[language=java] public static voud main(String[] args) { Thread[] threads = new Thread[10]; for(int i=0; i<10; i++){ @@ -4888,76 +4891,76 @@ public static voud main(String[] args) { } } \end{lstlisting} - -\subsubsection{Wechselseitiger Ausschluss in Java} -Schlüsselwort \textbf{synchronized} -\begin{itemize*} - \item Implementierung von sogenannten Monitoren bzw. locks (exklusiven Sperren) + + \subsubsection{Wechselseitiger Ausschluss in Java} + Schlüsselwort \textbf{synchronized} \begin{itemize*} - \item nur ein Thread darf den kritischen Abschnitt betreten - \item alle anderen Threads, die darauf zugrifen wollen, müssen auf Freigabe warten - \end{itemize*} - \item für Methoden: \textbf{public synchronized void doSomething()} - \begin{itemize*} - \item nur ein Thread darf diese Methode auf einem Objekt zur gleichen Zeit ausführen - \end{itemize*} - \item für Anweisungen: \textbf{synchronized(anObject)\{...\}} - \begin{itemize*} - \item nur ein Thread darf den Block betreten - \item Sperre wird durch das Objekt \textbf{anObject} verwaltet (jedem Java-Objekt ist eine Sperre zugeordnet) - \end{itemize*} -\end{itemize*} -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-java-synchronized} -\end{center} -\begin{itemize*} - \item Schlüsselwort synchronized - \begin{itemize*} - \item Implementierung von sogenannten Monitoren bzw. locks (exklusiven Sperren); nur ein Thread darf den kritischen Abschnitt betreten; alle anderen Threads, die darauf zugreifen wollen, müssen auf Freigabe warten - \item für Methoden: - \begin{lstlisting} - public synchronized void doSomething() - \end{lstlisting} + \item Implementierung von sogenannten Monitoren bzw. locks (exklusiven Sperren) + \begin{itemize*} + \item nur ein Thread darf den kritischen Abschnitt betreten + \item alle anderen Threads, die darauf zugrifen wollen, müssen auf Freigabe warten + \end{itemize*} + \item für Methoden: \textbf{public synchronized void doSomething()} \begin{itemize*} \item nur ein Thread darf diese Methode auf einem Objekt zur gleichen Zeit ausführen \end{itemize*} - \item für Anweisungen: synchronized(anObject) { ... } + \item für Anweisungen: \textbf{synchronized(anObject)\{...\}} \begin{itemize*} \item nur ein Thread darf den Block betreten - \item Sperre wird durch das Objekt anObject verwaltet (jedem Java-Objekt ist ein Sperre zugeordnet) + \item Sperre wird durch das Objekt \textbf{anObject} verwaltet (jedem Java-Objekt ist eine Sperre zugeordnet) \end{itemize*} \end{itemize*} -\end{itemize*} - -\subsubsection{wait \& notify} -\begin{description*} - \item[] Signalisierung zwischen Threads in Java - \item[] Basismethoden der Klasse \textbf{java.lang.Object} - \item[wait()] der aktive Thread wartet an diesem Objekt, Sperren werden ggf. freigegeben. - \item[notify()] wekct an diesem Objekt wartenden Thread auf - \item[notifyAll()] weckt alle an diesem Objekt wartenden Threads auf - \item[wait() \& notify()] dürfen nur in einem \textbf{synchronized}-Block aufgerufen werden -\end{description*} - -\subsubsection{Java: High-Level-Klassen} -\begin{itemize*} - \item Paket \textbf{java.util.concurrent} seit Java Version 1.5 - \item Abstraktionsschicht versteckt Details über Thread-Erzeugung - \item Übernimmt Erstellung und Überwachung von parallelen Tasks, u.a. + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-java-synchronized} + \end{center} + \begin{itemize*} + \item Schlüsselwort synchronized + \begin{itemize*} + \item Implementierung von sogenannten Monitoren bzw. locks (exklusiven Sperren); nur ein Thread darf den kritischen Abschnitt betreten; alle anderen Threads, die darauf zugreifen wollen, müssen auf Freigabe warten + \item für Methoden: + \begin{lstlisting} + public synchronized void doSomething() + \end{lstlisting} + \begin{itemize*} + \item nur ein Thread darf diese Methode auf einem Objekt zur gleichen Zeit ausführen + \end{itemize*} + \item für Anweisungen: synchronized(anObject) { ... } + \begin{itemize*} + \item nur ein Thread darf den Block betreten + \item Sperre wird durch das Objekt anObject verwaltet (jedem Java-Objekt ist ein Sperre zugeordnet) + \end{itemize*} + \end{itemize*} + \end{itemize*} + + \subsubsection{wait \& notify} \begin{description*} - \item[ExecutorService] zum erzeugen asynchroner Tasks - \item[Future] Referenz auf diesen Task bzw. dessen Ergebnis - \item[ForkJoinPool \& RecursiveAction] rekursives Aufteilen eines großen Problems + \item[] Signalisierung zwischen Threads in Java + \item[] Basismethoden der Klasse \textbf{java.lang.Object} + \item[wait()] der aktive Thread wartet an diesem Objekt, Sperren werden ggf. freigegeben. + \item[notify()] wekct an diesem Objekt wartenden Thread auf + \item[notifyAll()] weckt alle an diesem Objekt wartenden Threads auf + \item[wait() \& notify()] dürfen nur in einem \textbf{synchronized}-Block aufgerufen werden \end{description*} -\end{itemize*} - -\subsubsection{Tasks und Futures in Java} -\begin{itemize*} - \item Task = logische Ausführungseinheit - \item Thread = Mechanismus zur asynchronen/parallelen Ausführung von Tasks -\end{itemize*} - -\begin{lstlisting}[language=java] + + \subsubsection{Java: High-Level-Klassen} + \begin{itemize*} + \item Paket \textbf{java.util.concurrent} seit Java Version 1.5 + \item Abstraktionsschicht versteckt Details über Thread-Erzeugung + \item Übernimmt Erstellung und Überwachung von parallelen Tasks, u.a. + \begin{description*} + \item[ExecutorService] zum erzeugen asynchroner Tasks + \item[Future] Referenz auf diesen Task bzw. dessen Ergebnis + \item[ForkJoinPool \& RecursiveAction] rekursives Aufteilen eines großen Problems + \end{description*} + \end{itemize*} + + \subsubsection{Tasks und Futures in Java} + \begin{itemize*} + \item Task = logische Ausführungseinheit + \item Thread = Mechanismus zur asynchronen/parallelen Ausführung von Tasks + \end{itemize*} + + \begin{lstlisting}[language=java] Runnable task = () -> { String me = Thread.currentThread().getName(); System.out.println("Hallo " + me); @@ -4966,28 +4969,28 @@ task.run(); Thread thread = new Thread(task); thread.start(); \end{lstlisting} - -\subsubsection{Future \& ExecutorService} -\begin{itemize*} - \item \textbf{ExecutorService} stellt Methoden zum Starten/Beenden/Steuern von parallelen Aufgaben bereit - \item implementiert \textbf{Executor}-Interface + + \subsubsection{Future \& ExecutorService} \begin{itemize*} - \item definiert Methode \textbf{void execute(Runnable r)} + \item \textbf{ExecutorService} stellt Methoden zum Starten/Beenden/Steuern von parallelen Aufgaben bereit + \item implementiert \textbf{Executor}-Interface + \begin{itemize*} + \item definiert Methode \textbf{void execute(Runnable r)} + \end{itemize*} + \item Starten einer Aufgabe mit \textbf{submit} + \begin{itemize*} + \item \textbf{$Future$ submit(Callable c)} + \item \textbf{$Future$ submit(Runnable r)} + \end{itemize*} + \item Zugriff auf das Ergebnis mit \textbf{get} + \begin{itemize*} + \item \textbf{T get(long timeout, TimeUnit unit)} + \item \textbf{T get()} + \end{itemize*} \end{itemize*} - \item Starten einer Aufgabe mit \textbf{submit} - \begin{itemize*} - \item \textbf{$Future$ submit(Callable c)} - \item \textbf{$Future$ submit(Runnable r)} - \end{itemize*} - \item Zugriff auf das Ergebnis mit \textbf{get} - \begin{itemize*} - \item \textbf{T get(long timeout, TimeUnit unit)} - \item \textbf{T get()} - \end{itemize*} -\end{itemize*} - -\paragraph{Future \& ExecutorService: Beispiel} -\begin{lstlisting}[language=java] + + \paragraph{Future \& ExecutorService: Beispiel} + \begin{lstlisting}[language=java] class App { ExecutorService exevutor = Executors.newFixedThreadPool(4); void search(final String w) throws InterruptedException { @@ -5007,28 +5010,28 @@ class App { } } \end{lstlisting} - -\subsubsection{RecursiveAction \& Fork/Join} -\begin{itemize*} - \item Rekursives Zerlegen eines großen Problems in kleinere Probleme - \item Solange bis Problem klein genug um direkt ausgeführt werden zu können - \item Task erstellt zwei oder mehr Teiltasks von sich selbst $\rightarrowtail$ Datenparallelität - \item ForkJoinPool zum Ausführen $\rightarrow$ implementiert Executor Interface - \item Fazit + + \subsubsection{RecursiveAction \& Fork/Join} \begin{itemize*} - \item Parallelprogrammierung in Java sehr ähnlich zu C++ - \item Konzepte: Threads, kritische Abschnitte über synchronized - \item mächtige Abstraktionen in java.util.concurrent + \item Rekursives Zerlegen eines großen Problems in kleinere Probleme + \item Solange bis Problem klein genug um direkt ausgeführt werden zu können + \item Task erstellt zwei oder mehr Teiltasks von sich selbst $\rightarrowtail$ Datenparallelität + \item ForkJoinPool zum Ausführen $\rightarrow$ implementiert Executor Interface + \item Fazit \begin{itemize*} - \item Tasks und Futures, Executor und ThreadPool - \item thread-sichere Datenstrukturen - \item Synchronisation: Barrieren, Semaphoren, ... + \item Parallelprogrammierung in Java sehr ähnlich zu C++ + \item Konzepte: Threads, kritische Abschnitte über synchronized + \item mächtige Abstraktionen in java.util.concurrent + \begin{itemize*} + \item Tasks und Futures, Executor und ThreadPool + \item thread-sichere Datenstrukturen + \item Synchronisation: Barrieren, Semaphoren, ... + \end{itemize*} \end{itemize*} \end{itemize*} -\end{itemize*} - -\paragraph{Beispiel} -\begin{lstlisting}[language=java] + + \paragraph{Beispiel} + \begin{lstlisting}[language=java] class MyTask extends RecursiveAction { String[] source; int start, length; public MyTask(String[] src, int s, int l) { @@ -5047,227 +5050,227 @@ class MyTask extends RecursiveAction { } } \end{lstlisting} - -Starten der Verarbeitung: -\begin{enumerate*} - \item (große) Gesamtaufgabe erstellen - \item ForkJoinPool erstellen - \item Aufgabe vom Pool ausführen lassen -\end{enumerate*} -\begin{lstlisting}[language=java] + + Starten der Verarbeitung: + \begin{enumerate*} + \item (große) Gesamtaufgabe erstellen + \item ForkJoinPool erstellen + \item Aufgabe vom Pool ausführen lassen + \end{enumerate*} + \begin{lstlisting}[language=java] String[] src = ... MyTask t = new MyTask(src, 0, src.length); ForkJoinPool pool = new ForkJoinPool(); pool.invoke(t); \end{lstlisting} - -\subsection{Zusammenfassung} -\begin{itemize*} - \item Parallelprogrammierung als wichtige Technik zur Nutzung moderner Hardware (Multicore, GPU, ...) - \item verschiedene Architekturen und Programmiermodelle - \item Instruktions-, Daten- und Taskparallelität - \item Message Passing vs. gemeinsamer Speicher - \item Konzepte in Erlang, C++, Java - \item hoher Abstraktionsgrad funktionaler Sprachen - \item C++/Java: Thread-Modell und Synchronisation mit vielen weiteren Konzepten - \item höherwertige Abstraktion durch zusätzliche Bibliotheken und Programmierschnittstellen -\end{itemize*} - -\newpage -\section{Verteilte Programmierung} - -\subsection{Grundlagen} -\subsubsection{Lernziele} -\begin{itemize*} - \item Verständnis von Techniken verteilter Programmierung als Paradigma + + \subsection{Zusammenfassung} \begin{itemize*} - \item Modelle und Konzepte unabhängig von Programmiersprache und Betriebssystem - \item Herausforderungen und Besonderheiten verteilter Programme + \item Parallelprogrammierung als wichtige Technik zur Nutzung moderner Hardware (Multicore, GPU, ...) + \item verschiedene Architekturen und Programmiermodelle + \item Instruktions-, Daten- und Taskparallelität + \item Message Passing vs. gemeinsamer Speicher + \item Konzepte in Erlang, C++, Java + \item hoher Abstraktionsgrad funktionaler Sprachen + \item C++/Java: Thread-Modell und Synchronisation mit vielen weiteren Konzepten + \item höherwertige Abstraktion durch zusätzliche Bibliotheken und Programmierschnittstellen \end{itemize*} - \item Kennenlernen konkreter Konzepte und Mechanismen + + \newpage + \section{Verteilte Programmierung} + + \subsection{Grundlagen} + \subsubsection{Lernziele} \begin{itemize*} - \item praktische Beispiele in Java, Erlang und C++ - \item Bewertung und Vergleich verschiedener Plattformen + \item Verständnis von Techniken verteilter Programmierung als Paradigma + \begin{itemize*} + \item Modelle und Konzepte unabhängig von Programmiersprache und Betriebssystem + \item Herausforderungen und Besonderheiten verteilter Programme + \end{itemize*} + \item Kennenlernen konkreter Konzepte und Mechanismen + \begin{itemize*} + \item praktische Beispiele in Java, Erlang und C++ + \item Bewertung und Vergleich verschiedener Plattformen + \end{itemize*} \end{itemize*} -\end{itemize*} - -\subsubsection{Einordnung} -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-einordnung-programmierung} -\end{center} -\begin{itemize*} - \item mehrere Rechner - \item Prozesse auf verschiedenen Rechnern - \item Kommunikation über Knotengrenzen hinweg - \item Behandlung von Knoten- oder Netzwerkausfällen -\end{itemize*} - -\subsubsection{Ziele} -\begin{itemize*} - \item Bisher: - \begin{itemize*} - \item eine Maschine - \item Prozesse kommunizieren nur innerhalb dieser Maschine (shared Memory vs. Message Passing) - \end{itemize*} - \item Jetzt: + + \subsubsection{Einordnung} + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-einordnung-programmierung} + \end{center} \begin{itemize*} \item mehrere Rechner \item Prozesse auf verschiedenen Rechnern - \end{itemize*} - \item Erfordert: - \begin{itemize*} \item Kommunikation über Knotengrenzen hinweg \item Behandlung von Knoten- oder Netzwerkausfällen \end{itemize*} -\end{itemize*} - -\subsubsection{Motivation} -\begin{itemize*} - \item viele verschiedene Systeme (Knoten) zur Verfügung + + \subsubsection{Ziele} \begin{itemize*} - \item PC, Server, virtuelle Maschinen - \item Lastverteilung, Spezialisierung auf bestimmte Probleme + \item Bisher: + \begin{itemize*} + \item eine Maschine + \item Prozesse kommunizieren nur innerhalb dieser Maschine (shared Memory vs. Message Passing) + \end{itemize*} + \item Jetzt: + \begin{itemize*} + \item mehrere Rechner + \item Prozesse auf verschiedenen Rechnern + \end{itemize*} + \item Erfordert: + \begin{itemize*} + \item Kommunikation über Knotengrenzen hinweg + \item Behandlung von Knoten- oder Netzwerkausfällen + \end{itemize*} \end{itemize*} - \item Knoten sind über Netzwerke verbunden + + \subsubsection{Motivation} \begin{itemize*} - \item LAN(Wohnräume, Büros,...): bis zu 10 Gbit/s - \item MAN(Metropolitan Area Network, Behördennetze, dicht besiedelte Regionen): bis zu 10 Gbit/s - \item WAN(Wide Area Network, weltweite Vernetzung): hohe Kapazitäten zwischen den ISPs + \item viele verschiedene Systeme (Knoten) zur Verfügung + \begin{itemize*} + \item PC, Server, virtuelle Maschinen + \item Lastverteilung, Spezialisierung auf bestimmte Probleme + \end{itemize*} + \item Knoten sind über Netzwerke verbunden + \begin{itemize*} + \item LAN(Wohnräume, Büros,...): bis zu 10 Gbit/s + \item MAN(Metropolitan Area Network, Behördennetze, dicht besiedelte Regionen): bis zu 10 Gbit/s + \item WAN(Wide Area Network, weltweite Vernetzung): hohe Kapazitäten zwischen den ISPs + \end{itemize*} \end{itemize*} -\end{itemize*} - -Wofür werden verteilte Systeme eingesetzt? -\begin{itemize*} - \item Gemeinsame Nutzung von Ressourcen + + Wofür werden verteilte Systeme eingesetzt? \begin{itemize*} - \item Cloud-Umgebungen - \item verteilte Datenbanksysteme + \item Gemeinsame Nutzung von Ressourcen + \begin{itemize*} + \item Cloud-Umgebungen + \item verteilte Datenbanksysteme + \end{itemize*} + \item Teilaufgaben in großen Anwendungen + \begin{itemize*} + \item parallele Ausführung + \item getrennte Teilaufgaben (Micro-Services) + \end{itemize*} + \item Informationsaustausch + \begin{itemize*} + \item Email, Messenger + \item verteilte Algorithmen + \end{itemize*} \end{itemize*} - \item Teilaufgaben in großen Anwendungen + + \subsubsection{Software Architekturen} + \begin{enumerate*} + \item Früher: Hardware, Betriebssystem, Anwendung + \begin{itemize*} + \item Virtualisierung von Prozessor, Speicher, E/A Systemen + \item Interprozesskommunikation (IPC) + \end{itemize*} + \item Middlewaresysteme: Hardware, OS, Middleware, Anwendung + \begin{itemize*} + \item verteilte Dienste + \item Programmierparadigmen: RPC, Client/Server,... + \item Java, CORBA, ... + \end{itemize*} + \item Heute: Virtualisierung + \begin{itemize*} + \item VM Hypervisor: verstecken Hardware vor dem Betriebssystem + \item Docker: eine Anwendung pro Container + \end{itemize*} + \end{enumerate*} + + \subsubsection{Herausforderungen} \begin{itemize*} - \item parallele Ausführung - \item getrennte Teilaufgaben (Micro-Services) + \item viele verschiedene Computer/Server + \begin{itemize*} + \item verschiedene Betriebssysteme + \item unterschiedliche Leistungsfähigkeit + \end{itemize*} + \item Systemkomponenten müssen miteinander kommunizieren + \item verteilte Algorithmen: Nachrichten senden, empfangen, bestätigen, Synchronisation + \item Knotenausfälle behandeln \end{itemize*} - \item Informationsaustausch + \color{orange} $\Rightarrow$ brauchen Modelle zur Beschreibung der Kommunikation \color{black} + + \subsubsection{Anforderungen} + \color{orange} Anforderungen an Kommunikationsmodelle in ... \color{black} + \newline + ... verteilten Systemen \begin{itemize*} - \item Email, Messenger - \item verteilte Algorithmen + \item Korrektheit + \item Sicherheit + \item Verfügbarkeit + \item Skalierbarkeit + \item Heterogenität \end{itemize*} -\end{itemize*} - -\subsubsection{Software Architekturen} -\begin{enumerate*} - \item Früher: Hardware, Betriebssystem, Anwendung + + ...verteilten Verkehrsmanagementsystemen \begin{itemize*} - \item Virtualisierung von Prozessor, Speicher, E/A Systemen - \item Interprozesskommunikation (IPC) + \item Echtzeitfähigkeit + \item Offenheit + \item Korrektheit, Sicherheit + \item Skalierbarkeit, Verfügbarkeit \end{itemize*} - \item Middlewaresysteme: Hardware, OS, Middleware, Anwendung + + \color{orange} Anforderungen \color{black} an den Betrieb eines (großen) verteilten Systems \begin{itemize*} - \item verteilte Dienste - \item Programmierparadigmen: RPC, Client/Server,... - \item Java, CORBA, ... + \item (Last-)Skalierbarkeit (Scale-out): + \begin{itemize*} + \item viele kleine Server - statt eines großen + \item neue Server nach Bedarf hinzuzufügen + \end{itemize*} + \item Funktionssicherheit (Safety) / IT-Sicherheit (Security) + \item Fehlertoleranz / Verfügbarkeit + \begin{itemize*} + \item Ausfälle von einzelnen Knoten kompensieren + \item Redundante Verarbeitung + \end{itemize*} + \item Offenheit / Interoperabilität + \begin{itemize*} + \item neue Knoten und Systeme einfach integrieren + \end{itemize*} + \item Transparenz + \begin{itemize*} + \item verstecke die vielen Server vor den Nutzern + \end{itemize*} \end{itemize*} - \item Heute: Virtualisierung + + \subsection{Grundlagen verteilter Programmierung in Java und Erlang} + \subsubsection{Sockets} \begin{itemize*} - \item VM Hypervisor: verstecken Hardware vor dem Betriebssystem - \item Docker: eine Anwendung pro Container + \item Verteilte Programmierung: Wir müssen einen entfernten Computer ansprechen + \item benötigen: Adresse $\rightarrow$ IP-Adresse + \item da mehrere Dienste auf demselben Computer laufen lauscht jeder Dienst auf einem Port (Nummer) + \begin{itemize*} + \item Wichtige Ports: 80 WWW, 20 (FTP), 25 (SMTP) + \end{itemize*} + \item \textbf{Socket} beschreibt einen Endpunkt, dh. Adresse und Port in einem TCP (oder UDP) Netzwerk + \item \textbf{Server-Socket} wartet auf Verbindungen + \item \textbf{Client} initiiert Verbindung, ebenfalls über einen (Client-Socket) \end{itemize*} -\end{enumerate*} - -\subsubsection{Herausforderungen} -\begin{itemize*} - \item viele verschiedene Computer/Server + + \subsubsection{Sockets in Java} + Socket in dem package ''java.net.Socket'' \begin{itemize*} - \item verschiedene Betriebssysteme - \item unterschiedliche Leistungsfähigkeit - \end{itemize*} - \item Systemkomponenten müssen miteinander kommunizieren - \item verteilte Algorithmen: Nachrichten senden, empfangen, bestätigen, Synchronisation - \item Knotenausfälle behandeln -\end{itemize*} -\color{orange} $\Rightarrow$ brauchen Modelle zur Beschreibung der Kommunikation \color{black} - -\subsubsection{Anforderungen} -\color{orange} Anforderungen an Kommunikationsmodelle in ... \color{black} -\newline -... verteilten Systemen -\begin{itemize*} - \item Korrektheit - \item Sicherheit - \item Verfügbarkeit - \item Skalierbarkeit - \item Heterogenität -\end{itemize*} - -...verteilten Verkehrsmanagementsystemen -\begin{itemize*} - \item Echtzeitfähigkeit - \item Offenheit - \item Korrektheit, Sicherheit - \item Skalierbarkeit, Verfügbarkeit -\end{itemize*} - -\color{orange} Anforderungen \color{black} an den Betrieb eines (großen) verteilten Systems -\begin{itemize*} - \item (Last-)Skalierbarkeit (Scale-out): - \begin{itemize*} - \item viele kleine Server - statt eines großen - \item neue Server nach Bedarf hinzuzufügen - \end{itemize*} - \item Funktionssicherheit (Safety) / IT-Sicherheit (Security) - \item Fehlertoleranz / Verfügbarkeit - \begin{itemize*} - \item Ausfälle von einzelnen Knoten kompensieren - \item Redundante Verarbeitung - \end{itemize*} - \item Offenheit / Interoperabilität - \begin{itemize*} - \item neue Knoten und Systeme einfach integrieren - \end{itemize*} - \item Transparenz - \begin{itemize*} - \item verstecke die vielen Server vor den Nutzern - \end{itemize*} -\end{itemize*} - -\subsection{Grundlagen verteilter Programmierung in Java und Erlang} -\subsubsection{Sockets} -\begin{itemize*} - \item Verteilte Programmierung: Wir müssen einen entfernten Computer ansprechen - \item benötigen: Adresse $\rightarrow$ IP-Adresse - \item da mehrere Dienste auf demselben Computer laufen lauscht jeder Dienst auf einem Port (Nummer) - \begin{itemize*} - \item Wichtige Ports: 80 WWW, 20 (FTP), 25 (SMTP) - \end{itemize*} - \item \textbf{Socket} beschreibt einen Endpunkt, dh. Adresse und Port in einem TCP (oder UDP) Netzwerk - \item \textbf{Server-Socket} wartet auf Verbindungen - \item \textbf{Client} initiiert Verbindung, ebenfalls über einen (Client-Socket) -\end{itemize*} - -\subsubsection{Sockets in Java} -Socket in dem package ''java.net.Socket'' -\begin{itemize*} - \item einen \textbf{ServerSocket} auf Port 4242 erstellen - \begin{lstlisting}[language=java] + \item einen \textbf{ServerSocket} auf Port 4242 erstellen + \begin{lstlisting}[language=java] ServerSocket serverSocket = new ServerSocket(4242); \end{lstlisting} - \item Warte \color{orange}blockierend \color{black} auf Verbindungen - \begin{lstlisting}[language=java] + \item Warte \color{orange}blockierend \color{black} auf Verbindungen + \begin{lstlisting}[language=java] Socket client = serverSocket.accept(); \end{lstlisting} - \item Client Socket erstellen und zum Server verbinden - \begin{lstlisting}[language=java] + \item Client Socket erstellen und zum Server verbinden + \begin{lstlisting}[language=java] Socket client = new Socket("localhost",4242); \end{lstlisting} - \item Sockets in ähnlicher Form in C++ -\end{itemize*} - -\paragraph{Sockets in Java - Beispiel} - -Echo Server (Serverseite) -\begin{lstlisting}[language=C++] + \item Sockets in ähnlicher Form in C++ + \end{itemize*} + + \paragraph{Sockets in Java - Beispiel} + + Echo Server (Serverseite) + \begin{lstlisting}[language=C++] ServerSocket server = new ServerSocket(4242); while(true){ try(Socket client = server.accept(); ){ @@ -5279,9 +5282,9 @@ Echo Server (Serverseite) catch ( Exception e) { e.printStackTrace(); } } \end{lstlisting} - -Echo Server (Clientseite) -\begin{lstlisting}[language=C++] + + Echo Server (Clientseite) + \begin{lstlisting}[language=C++] try(Socket server = new Socket("localhost", 4242); ){ Scanner in = new Scanner(client.getInputStream() ); PrintWriter out = new PrintWriter(server.getOutputStream(), true); @@ -5290,86 +5293,86 @@ Echo Server (Clientseite) } catch ( Exception e) { e.printStackTrace(); } \end{lstlisting} - -\subsection{Aktormodell in Erlang} -\begin{itemize*} - \item formales Modell für Nebenläufigkeit und Verteilung - \item Basis für verschiedene Programmiersprachen/Frameworks: Erlang, Akka (Scala/Java) - \item Prinzipien: + + \subsection{Aktormodell in Erlang} \begin{itemize*} - \item Aktor kapselt Zustand und Verhalten - \item Aktoren sind aktiv - \item Aktoren kommunizieren durch Nachrichtenaustausch + \item formales Modell für Nebenläufigkeit und Verteilung + \item Basis für verschiedene Programmiersprachen/Frameworks: Erlang, Akka (Scala/Java) + \item Prinzipien: \begin{itemize*} - \item Nichtblockierendes Senden - \item Blockierendes Empfangen + \item Aktor kapselt Zustand und Verhalten + \item Aktoren sind aktiv + \item Aktoren kommunizieren durch Nachrichtenaustausch + \begin{itemize*} + \item Nichtblockierendes Senden + \item Blockierendes Empfangen + \end{itemize*} \end{itemize*} \end{itemize*} -\end{itemize*} - -\subsubsection{Übersicht} -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Aktormodell.png} -\end{center} - -\begin{itemize*} - \item Aktormodell in Erlang nativ umgesetzt + + \subsubsection{Übersicht} + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-Aktormodell.png} + \end{center} + \begin{itemize*} - \item Sende- und Empfangsoperationen schon für parallele Programmierung benutzt - \item bisher aber nur auf einem Knoten + \item Aktormodell in Erlang nativ umgesetzt + \begin{itemize*} + \item Sende- und Empfangsoperationen schon für parallele Programmierung benutzt + \item bisher aber nur auf einem Knoten + \end{itemize*} + \item Programmbestandteile im Aktormodell + \begin{itemize*} + \item Verhaltensdefinition $\Rightarrow$ f() $\rightarrow$ ... end. + \item Erzeugen neuer Aktoren $\Rightarrow$ Pid = spwan(fun ...). + \item Empfangen von Nachrichten $\Rightarrow$ receive ... end. + \item Senden $\Rightarrow$ Pid ! Request. + \end{itemize*} + \item kein globaler Zustand \end{itemize*} - \item Programmbestandteile im Aktormodell - \begin{itemize*} - \item Verhaltensdefinition $\Rightarrow$ f() $\rightarrow$ ... end. - \item Erzeugen neuer Aktoren $\Rightarrow$ Pid = spwan(fun ...). - \item Empfangen von Nachrichten $\Rightarrow$ receive ... end. - \item Senden $\Rightarrow$ Pid ! Request. - \end{itemize*} - \item kein globaler Zustand -\end{itemize*} - -\subsubsection{Kommunikation zwischen Erlangknoten} -Erlangknoten starten (sname = short name) -\begin{lstlisting}[language=erlang] + + \subsubsection{Kommunikation zwischen Erlangknoten} + Erlangknoten starten (sname = short name) + \begin{lstlisting}[language=erlang] #erl -sname node1 -setcookie 1234 Eshell V11.0 (abort with ^G) (node1@localhost)1> \end{lstlisting} - -Weiteren Erlangknoten starten (selber oder anderer PC) -\begin{lstlisting}[language=erlang] + + Weiteren Erlangknoten starten (selber oder anderer PC) + \begin{lstlisting}[language=erlang] #erl -sname node2 -setcookie 1234 Eshell V11.0 (abort with ^G) (node2@localhost)1> \end{lstlisting} - -Liste der verbundenen Knoten -\begin{lstlisting}[language=erlang] + + Liste der verbundenen Knoten + \begin{lstlisting}[language=erlang] (node@localhost)1> nodes(). [] \end{lstlisting} - -\subsubsection{Cookie-System} -\begin{itemize*} - \item verteilte Erlangknoten benötigen zur Kommunikation gemeinsames \color{orange} Magic Cookie \color{black} (Passwort) - \item Mehrere Varianten + + \subsubsection{Cookie-System} \begin{itemize*} - \item Datei {\raise.17ex\hbox{$\scriptstyle\mathtt{\sim}$}}/.erlang.cookie - \item Erlang-Funktion - \begin{lstlisting}[language=erlang] + \item verteilte Erlangknoten benötigen zur Kommunikation gemeinsames \color{orange} Magic Cookie \color{black} (Passwort) + \item Mehrere Varianten + \begin{itemize*} + \item Datei {\raise.17ex\hbox{$\scriptstyle\mathtt{\sim}$}}/.erlang.cookie + \item Erlang-Funktion + \begin{lstlisting}[language=erlang] :set_cookie(node(), Cookie). \end{lstlisting} - \item Option - \begin{lstlisting} + \item Option + \begin{lstlisting} erl -setcookie Cookie \end{lstlisting} + \end{itemize*} \end{itemize*} -\end{itemize*} - -\subsubsection{Verbindungsaufbau zwischen Erlangknoten} -Verbindungsaufbau mittels \color{blue} net\_adm:\color{black}ping Funktion - -\begin{lstlisting}[language=erlang] + + \subsubsection{Verbindungsaufbau zwischen Erlangknoten} + Verbindungsaufbau mittels \color{blue} net\_adm:\color{black}ping Funktion + + \begin{lstlisting}[language=erlang] (node1@localhost)2> net_adm:ping("node2@localhost"). pong (node1@localhost)3> nodes(). @@ -5378,11 +5381,11 @@ Verbindungsaufbau mittels \color{blue} net\_adm:\color{black}ping Funktion (node2@localhost)1> nodes(). ["node1@localhost"] \end{lstlisting} - -\subsubsection{Kommunikation zwischen Erlangknoten} -Starten eines Prozesses auf einem entfernten Host - -\begin{lstlisting}[language=erlang] + + \subsubsection{Kommunikation zwischen Erlangknoten} + Starten eines Prozesses auf einem entfernten Host + + \begin{lstlisting}[language=erlang] complicated() -> receive {Sender, I} -> Sender ! I*I @@ -5397,45 +5400,45 @@ Starten eines Prozesses auf einem entfernten Host Result = 625 ok \end{lstlisting} - -\subsection{Alternating Bit Protokoll} -\subsubsection{Übersicht} -\begin{itemize*} - \item ermöglicht es, Nachrichten über einen verlustbehafteten Kommunikationskanal vollständig zu übertragen, sofern Verluste nur gelegentlich auftreten (transiente Fehler) - \item Empfänger quittiert jedes erhaltene Paket (Achnowledgement, kurz ACK) - \item \textbf{Achtung} Kanal kann Nachrichten und ACKs verlieren + + \subsection{Alternating Bit Protokoll} + \subsubsection{Übersicht} \begin{itemize*} - \item benötigen je zwei unterschiedliche Sequenznummern und ACKs + \item ermöglicht es, Nachrichten über einen verlustbehafteten Kommunikationskanal vollständig zu übertragen, sofern Verluste nur gelegentlich auftreten (transiente Fehler) + \item Empfänger quittiert jedes erhaltene Paket (Achnowledgement, kurz ACK) + \item \textbf{Achtung} Kanal kann Nachrichten und ACKs verlieren + \begin{itemize*} + \item benötigen je zwei unterschiedliche Sequenznummern und ACKs + \end{itemize*} + \item Empfänger liefert eine Nachricht nur beim ersten Empfang aus (keine Duplikate) + \item bei Timeout: Nachricht erneut senden + \item bei Erhalt eines unerwarteten ACKs: \textbf{aktuelle Nachricht erneut senden} \end{itemize*} - \item Empfänger liefert eine Nachricht nur beim ersten Empfang aus (keine Duplikate) - \item bei Timeout: Nachricht erneut senden - \item bei Erhalt eines unerwarteten ACKs: \textbf{aktuelle Nachricht erneut senden} -\end{itemize*} - -\subsubsection{Zustände} -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-zustände-bit-protokoll} -\end{center} - -\subsubsection{Das Alternating Bit Protokoll} -Wir implementieren eine Variante, bei welcher: -\begin{itemize*} - \item der Sender zu Beginn eine Liste mit sämtlichen zu sendenden Nachrichten erhält, und - \item der Empfänger die erstmals empfangenen Nachrichten einfach auf dem Bildschirm ausgibt - \item alle Aktoren Statusmeldungen ausgeben - \item Verluste über einen Zufallszahlengenerator ausgelöst werden -\end{itemize*} -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-alternate-bit-protokoll.png} -\end{center} -drei Prozesse mit \textbf{initialize(ErrorRate, NumberOfMessages, ReceiverPid, SenderPid, ChannelPid)} initialisieren und starten -\begin{itemize*} - \item Sender hat vier Zustandsfunktionen; Startet mit senderReady0(List): Liste mit Zahlen 1,...,NumberOfMessages - \item Kanal: Nachricht 'verlieren', wenn Zufallszahl $\ngeq$ ErrorRate - \item Empfänger hat zwei Zustandsfunktionen; zu Beginn wird receiverWait0 gestartet - \item initialize wartet auf eine ready-Nachricht; sendet danach stop-Nachrichten an alle -\end{itemize*} -\begin{lstlisting}[language=erlang] + + \subsubsection{Zustände} + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-zustände-bit-protokoll} + \end{center} + + \subsubsection{Das Alternating Bit Protokoll} + Wir implementieren eine Variante, bei welcher: + \begin{itemize*} + \item der Sender zu Beginn eine Liste mit sämtlichen zu sendenden Nachrichten erhält, und + \item der Empfänger die erstmals empfangenen Nachrichten einfach auf dem Bildschirm ausgibt + \item alle Aktoren Statusmeldungen ausgeben + \item Verluste über einen Zufallszahlengenerator ausgelöst werden + \end{itemize*} + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-alternate-bit-protokoll.png} + \end{center} + drei Prozesse mit \textbf{initialize(ErrorRate, NumberOfMessages, ReceiverPid, SenderPid, ChannelPid)} initialisieren und starten + \begin{itemize*} + \item Sender hat vier Zustandsfunktionen; Startet mit senderReady0(List): Liste mit Zahlen 1,...,NumberOfMessages + \item Kanal: Nachricht 'verlieren', wenn Zufallszahl $\ngeq$ ErrorRate + \item Empfänger hat zwei Zustandsfunktionen; zu Beginn wird receiverWait0 gestartet + \item initialize wartet auf eine ready-Nachricht; sendet danach stop-Nachrichten an alle + \end{itemize*} + \begin{lstlisting}[language=erlang] -module(altbit). -export([initialize/5]). -import(rand, [seed/3, uniform/0]). @@ -5443,8 +5446,8 @@ drei Prozesse mit \textbf{initialize(ErrorRate, NumberOfMessages, ReceiverPid, S for(Max, Max, F) -> [F(Max)]; % for convemience for(I, Max, F) -> [F(I)|for(I+1, Max, F)] \end{lstlisting} - -\begin{lstlisting}[language=erlang] + + \begin{lstlisting}[language=erlang] initialize(ErrorRate, NumberOfMessages, R, S, C) -> rand:seed({23, 13, 97}), %initialised RND SendList = for(1, NumberOfMessages, fun(I) -> I end), @@ -5462,16 +5465,16 @@ initialize(ErrorRate, NumberOfMessages, R, S, C) -> Sender ! stop, Receiver ! stop, Channel ! stop, unregister(initializer) \end{lstlisting} - -\begin{lstlisting}[language=erlang] + + \begin{lstlisting}[language=erlang] senderReady0([]) -> initializer ! ready; senderReady0([M|MS]) -> channel ! {receiver, {seq0, M}}, io:format("Sender: sends Message ~.10B. ~n",[M]), senderProcess0([M|MS]). \end{lstlisting} - -\begin{lstlisting}[language=erlang] + + \begin{lstlisting}[language=erlang] senderProcess0([]) -> initializer!ready; %to be safe senderProcess0([M|MS]) -> receive @@ -5485,8 +5488,8 @@ senderProcess0([M|MS]) -> senderProcess0([M|MS]) end. \end{lstlisting} - -\begin{lstlisting}[language=erlang] + + \begin{lstlisting}[language=erlang] cannelIdle(ErrorRate) -> RN = uniform(), % for determining if msg to be dropped receive @@ -5505,8 +5508,8 @@ cannelIdle(ErrorRate) -> stop -> true end. \end{lstlisting} - -\begin{lstlisting}[language=erlang] + + \begin{lstlisting}[language=erlang] receiverWait0() -> receive {seq0, M} -> @@ -5520,8 +5523,8 @@ receiverWait0() -> stop -> true end. \end{lstlisting} - -\begin{lstlisting}[language=bash] + + \begin{lstlisting}[language=bash] > altbit:initialize(0.45, 3, 'receiver@pc1', 'channel@pc2', 'sender@pc3'). Started ABP with 3 Messages and Error-Rate 0.450000 Sender: sends Message 1. @@ -5542,224 +5545,224 @@ Receiver: received and delivers Message 3. Sender: received expected Ack for Message 3. All 3 Messages successfully transmitted. \end{lstlisting} - -\subsection{Kommunikationsmodelle \& Implementierungen} -\subsubsection{Kommunikationsmodelle} -Frage: Wie sollen Knoten miteinander kommunizieren? -\begin{itemize*} - \item Sprechen die Teilnehmer direkt miteinander oder über einen Vermittler? - \item Kann jeder jedem eine Nachricht schicken? - \item Wartet ein Teilnehmer darauf, dass seine Nachricht angekommen ist? - \item Wartet ein Teilnehmer darauf, dass eine Nachricht ankommt? - \item Muss ein Teilnehmer auf eine Nachricht antworten? -\end{itemize*} + + \subsection{Kommunikationsmodelle \& Implementierungen} + \subsubsection{Kommunikationsmodelle} + Frage: Wie sollen Knoten miteinander kommunizieren? + \begin{itemize*} + \item Sprechen die Teilnehmer direkt miteinander oder über einen Vermittler? + \item Kann jeder jedem eine Nachricht schicken? + \item Wartet ein Teilnehmer darauf, dass seine Nachricht angekommen ist? + \item Wartet ein Teilnehmer darauf, dass eine Nachricht ankommt? + \item Muss ein Teilnehmer auf eine Nachricht antworten? + \end{itemize*} $\Rightarrow$ das Verhalten der Teilnehmer ist in \color{orange} Kommunikationsmodellen \color{black} beschrieben - -\subsubsection{Arten von Kommunikationsmodellen} -Es gibt viele verschiedene Modelle, z.B. für \color{orange} Botschaftenbasierte Modelle \color{black} -\begin{itemize*} - \item Auftragsorientierte Modelle - \item Funktionsaufrufbasierte Modelle - \item Blackboards - \item Ereignisbasierte Modelle - \item Strombasierte Modelle - \item Wissensbasierte Modelle -\end{itemize*} -Kommunikationspartner sind für uns: -\begin{itemize*} - \item Threads/Prozesse innerhalb verteilter Anwendungen - \item Komponenten verteilter Systeme (Browser $\Leftrightarrow$ Webserver, DB Client $\Leftrightarrow$ DB-Server) -\end{itemize*} - -\subsubsection{Modellbestandteile} -\begin{itemize*} - \item \color{orange} Rollenmodell: \color{black} + + \subsubsection{Arten von Kommunikationsmodellen} + Es gibt viele verschiedene Modelle, z.B. für \color{orange} Botschaftenbasierte Modelle \color{black} \begin{itemize*} - \item gemeinsames Handlungsmuster festlegen - \item z.B. Anrufer/Angerufener, Clinet/Server, Quelle/Senke + \item Auftragsorientierte Modelle + \item Funktionsaufrufbasierte Modelle + \item Blackboards + \item Ereignisbasierte Modelle + \item Strombasierte Modelle + \item Wissensbasierte Modelle \end{itemize*} - \item \color{orange} Datenmodell: \color{black} + Kommunikationspartner sind für uns: \begin{itemize*} - \item einheitliche Interpretation der ausgetauschten Daten - \item z.B. Dateiformate (XML/JSON), Kodierungen (MPEG4/H.264) + \item Threads/Prozesse innerhalb verteilter Anwendungen + \item Komponenten verteilter Systeme (Browser $\Leftrightarrow$ Webserver, DB Client $\Leftrightarrow$ DB-Server) \end{itemize*} - \item \color{orange} Fehlersemantiken \color{black} + + \subsubsection{Modellbestandteile} \begin{itemize*} - \item Einvernehmen über Wirkungen von Ausfällen - \item Eigenschaften von Kommunikationsoperationen müssen bei Ausfällen garantiert werden - \end{itemize*} - \item \color{orange} Terminierungssemantik \color{black} - \begin{itemize*} - \item Einvernehmen über das Ende der Kommunikation - \item Garantien über das Ende von Kommunikationsoperationen (auch bei Ausfällen) - \end{itemize*} -\end{itemize*} - -\subsubsection{Kommunikationsarten} -\begin{itemize*} - \item Wann ist eine Kommunikationsoperation abgeschlossen? - \item entspricht Terminierungssemantik - \item zwei grundlegende Arten: - \begin{itemize*} - \item \color{orange} synchron \color{black} + \item \color{orange} Rollenmodell: \color{black} \begin{itemize*} - \item blockierend - \item Teilnehmer wartet bis die Gegenseite bereit ist - \item kann lange dauern, Sender kann nicht weiter arbeiten - \item Senden: Botschaftenankunft garantiert, einfache Implementierung synchroner Aktivitäten - \item Empfangen: Botschaftenankunft einfach und präzise feststellbar + \item gemeinsames Handlungsmuster festlegen + \item z.B. Anrufer/Angerufener, Clinet/Server, Quelle/Senke \end{itemize*} - \item \color{orange} asynchron \color{black} + \item \color{orange} Datenmodell: \color{black} \begin{itemize*} - \item nicht-blockierend - \item Der Teilnehmer wartet nicht auf die Gegenseite (”fire and forget”) - \item unklar ob Botschaft angekommen - \item Senden: einfache Implementierung von Nebenläufigkeit - \item Empfangen: unklar wann Botschaft ankommt, einfache Implementierung von Nebenläufigkeit + \item einheitliche Interpretation der ausgetauschten Daten + \item z.B. Dateiformate (XML/JSON), Kodierungen (MPEG4/H.264) \end{itemize*} - \item gilt sowohl für das Senden als auch das Empfangen - \end{itemize*} -\end{itemize*} - -\subsubsection{Kommunikationsarten: Senden} - -\color{orange} synchrones Senden: \color{black} Der Sender wartet bis der Empfänger die Botschaft annimmt -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-synchrones-senden} -\end{center} -\noindent \color{orange} asynchrones Senden: \color{black} Der Sender wartet nicht bis der Empfänger die Botschaft annimmt ('fire and forget' Prinzip) -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-asynchrones-senden} -\end{center} - -\paragraph{Synchrones vs. asynchrones Senden} - -\begin{itemize*} - \item synchrones Senden - \begin{itemize*} - \item kann lange dauern, der Sender kann währenddessen nicht weiterarbeiten - \item die Botschaftenankunft ist garantiert, eine einfache Implementierung synchroner Aktivitäten - \end{itemize*} - \item asynchrones Senden - \begin{itemize*} - \item unklar ob die Botschaft angekommen ist - \item einfache Implementierung von Nebenläufigkeit - \end{itemize*} -\end{itemize*} - -\subsubsection{Kommunikationsarten: Empfangen} - -\color{orange} synchrones Empfangen: \color{black} Der Empfänger wartet bis die Botschaft eintrifft -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-synchron-empfangen} -\end{center} -\color{orange} asynchrones Empfangen: \color{black} Der Empfänger macht weiter, falls keine Nachricht eingetroffen ist -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-asynchron-empfangen} -\end{center} - -\paragraph{Synchrones vs. asynchrones Empfangen} -\begin{itemize*} - \item synchrones Empfangen: - \begin{itemize*} - \item kann lange dauern, der Sender kann nicht weiterarbeiten - \item Botschaftenankunft ist einfach und präzise feststellbar - \end{itemize*} - \item asynchrones Empfangen: - \begin{itemize*} - \item unklar wann die Botschaft ankommt; - \newline Benachrichtigungstechniken + \item \color{orange} Fehlersemantiken \color{black} \begin{itemize*} - \item Nachfragen (Polling) - \item ankommende Botschaft erzeugt neuen Thread beim Empfänger - \item weitere Techniken möglich + \item Einvernehmen über Wirkungen von Ausfällen + \item Eigenschaften von Kommunikationsoperationen müssen bei Ausfällen garantiert werden + \end{itemize*} + \item \color{orange} Terminierungssemantik \color{black} + \begin{itemize*} + \item Einvernehmen über das Ende der Kommunikation + \item Garantien über das Ende von Kommunikationsoperationen (auch bei Ausfällen) \end{itemize*} - \item einfache Implementierung von Nebenläufigkeit \end{itemize*} -\end{itemize*} - -\subsubsection{Fehlerbehandlung} -\begin{itemize*} - \item unverlässliches vs. verlässliches Senden + + \subsubsection{Kommunikationsarten} \begin{itemize*} - \item 'Brief vs. Einschreiben' + \item Wann ist eine Kommunikationsoperation abgeschlossen? + \item entspricht Terminierungssemantik + \item zwei grundlegende Arten: + \begin{itemize*} + \item \color{orange} synchron \color{black} + \begin{itemize*} + \item blockierend + \item Teilnehmer wartet bis die Gegenseite bereit ist + \item kann lange dauern, Sender kann nicht weiter arbeiten + \item Senden: Botschaftenankunft garantiert, einfache Implementierung synchroner Aktivitäten + \item Empfangen: Botschaftenankunft einfach und präzise feststellbar + \end{itemize*} + \item \color{orange} asynchron \color{black} + \begin{itemize*} + \item nicht-blockierend + \item Der Teilnehmer wartet nicht auf die Gegenseite (”fire and forget”) + \item unklar ob Botschaft angekommen + \item Senden: einfache Implementierung von Nebenläufigkeit + \item Empfangen: unklar wann Botschaft ankommt, einfache Implementierung von Nebenläufigkeit + \end{itemize*} + \item gilt sowohl für das Senden als auch das Empfangen + \end{itemize*} \end{itemize*} - \item verlässliche Kommunikation erfordert + + \subsubsection{Kommunikationsarten: Senden} + + \color{orange} synchrones Senden: \color{black} Der Sender wartet bis der Empfänger die Botschaft annimmt + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-synchrones-senden} + \end{center} + \noindent \color{orange} asynchrones Senden: \color{black} Der Sender wartet nicht bis der Empfänger die Botschaft annimmt ('fire and forget' Prinzip) + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-asynchrones-senden} + \end{center} + + \paragraph{Synchrones vs. asynchrones Senden} + \begin{itemize*} - \item Quittierungen (Acknowledgements) $\rightarrow$ mehr Daten senden - \item Timeouts $\rightarrow$ Zeitverwaltung, langes Warten + \item synchrones Senden + \begin{itemize*} + \item kann lange dauern, der Sender kann währenddessen nicht weiterarbeiten + \item die Botschaftenankunft ist garantiert, eine einfache Implementierung synchroner Aktivitäten + \end{itemize*} + \item asynchrones Senden + \begin{itemize*} + \item unklar ob die Botschaft angekommen ist + \item einfache Implementierung von Nebenläufigkeit + \end{itemize*} \end{itemize*} - \item vielfältige Fehlermöglichkeiten in verteilten Anwendungen: + + \subsubsection{Kommunikationsarten: Empfangen} + + \color{orange} synchrones Empfangen: \color{black} Der Empfänger wartet bis die Botschaft eintrifft + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-synchron-empfangen} + \end{center} + \color{orange} asynchrones Empfangen: \color{black} Der Empfänger macht weiter, falls keine Nachricht eingetroffen ist + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-asynchron-empfangen} + \end{center} + + \paragraph{Synchrones vs. asynchrones Empfangen} \begin{itemize*} - \item Kommunikations-/Netzwerkfehler: $\rightarrow$ Nachricht/Antwort gar nicht oder verzögert zugestellt + \item synchrones Empfangen: + \begin{itemize*} + \item kann lange dauern, der Sender kann nicht weiterarbeiten + \item Botschaftenankunft ist einfach und präzise feststellbar + \end{itemize*} + \item asynchrones Empfangen: + \begin{itemize*} + \item unklar wann die Botschaft ankommt; + \newline Benachrichtigungstechniken + \begin{itemize*} + \item Nachfragen (Polling) + \item ankommende Botschaft erzeugt neuen Thread beim Empfänger + \item weitere Techniken möglich + \end{itemize*} + \item einfache Implementierung von Nebenläufigkeit + \end{itemize*} + \end{itemize*} + + \subsubsection{Fehlerbehandlung} + \begin{itemize*} + \item unverlässliches vs. verlässliches Senden + \begin{itemize*} + \item 'Brief vs. Einschreiben' + \end{itemize*} + \item verlässliche Kommunikation erfordert + \begin{itemize*} + \item Quittierungen (Acknowledgements) $\rightarrow$ mehr Daten senden + \item Timeouts $\rightarrow$ Zeitverwaltung, langes Warten + \end{itemize*} + \item vielfältige Fehlermöglichkeiten in verteilten Anwendungen: + \begin{itemize*} + \item Kommunikations-/Netzwerkfehler: $\rightarrow$ Nachricht/Antwort gar nicht oder verzögert zugestellt + \item Serverausfall: Nachricht empfangen? Operation ausgeführt? + \item Clientausfall: Aufruf gültig? Bestätigung erhalten? + \end{itemize*} + \end{itemize*} + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-kommunikation-fehler} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-kommunikation-fehler-2} + \end{center} + + vielfältige Fehlermöglichkeiten in verteilten Anwendungen: + \begin{itemize*} + \item Kommunikations-/Netzwerkfehler: $\rightarrow$ Nachricht/Antwort gar nicht oder nur verzögert zugestellt \item Serverausfall: Nachricht empfangen? Operation ausgeführt? \item Clientausfall: Aufruf gültig? Bestätigung erhalten? + \item Beispiel: Reisebuchung + \begin{itemize*} + \item Buchung durchgeführt? Bestätigung erhalten? + \item Bei wiederholter Ausführung: wirklich neue Buchung? + \end{itemize*} \end{itemize*} -\end{itemize*} -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-kommunikation-fehler} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-kommunikation-fehler-2} -\end{center} - -vielfältige Fehlermöglichkeiten in verteilten Anwendungen: -\begin{itemize*} - \item Kommunikations-/Netzwerkfehler: $\rightarrow$ Nachricht/Antwort gar nicht oder nur verzögert zugestellt - \item Serverausfall: Nachricht empfangen? Operation ausgeführt? - \item Clientausfall: Aufruf gültig? Bestätigung erhalten? - \item Beispiel: Reisebuchung + + \paragraph{Fehlerbehandlung in Erlang} + \begin{itemize*} - \item Buchung durchgeführt? Bestätigung erhalten? - \item Bei wiederholter Ausführung: wirklich neue Buchung? + \item Timeout beim Warten auf Nachrichten + \item Wenn keine passende Nachricht innerhalb \textbf{Time} msecs empfangen wird, dann wird der Rückgabewert des \textbf{after}-Ausdrucks verwendet. \end{itemize*} -\end{itemize*} - -\paragraph{Fehlerbehandlung in Erlang} - -\begin{itemize*} - \item Timeout beim Warten auf Nachrichten - \item Wenn keine passende Nachricht innerhalb \textbf{Time} msecs empfangen wird, dann wird der Rückgabewert des \textbf{after}-Ausdrucks verwendet. -\end{itemize*} -\begin{lstlisting}[language=erlang] + \begin{lstlisting}[language=erlang] receive {ok, Resp} -> Resp; {notfound} -> notfound; after Time -> timeout end \end{lstlisting} - -Umgang mit Fehlern (Timeouts, Ausfälle): -\begin{itemize*} - \item Maybe: + + Umgang mit Fehlern (Timeouts, Ausfälle): \begin{itemize*} - \item keine Wiederholung - \item keine Ausführungsgarantie + \item Maybe: + \begin{itemize*} + \item keine Wiederholung + \item keine Ausführungsgarantie + \end{itemize*} + \item At-least-once: + \begin{itemize*} + \item wiederholte Ausführung, aber keine Erkennung von Nachrichtduplikaten + \item nur für idempotente Operationen (Lesen) + \end{itemize*} + \item At-most-once: + \begin{itemize*} + \item garantiert, dass mehrfache Aufrufe nur zu einziger Ausführung führen + \item z.B. durch Sequenznummern (erfordert Protokollierung zur Duplikateliminierung) + \item für nicht-idempotente Operationen (schreibend, z.B. Einfügen, Löschen) + \end{itemize*} \end{itemize*} - \item At-least-once: + + \subsubsection{Überwachung von Erlang-Prozessen} \begin{itemize*} - \item wiederholte Ausführung, aber keine Erkennung von Nachrichtduplikaten - \item nur für idempotente Operationen (Lesen) + \item Linking von Prozessen: \color{green}link\color{blue}(Pid) \color{black} + \item M überwacht S; S bricht durch Fehler ab + \begin{center} + \centering + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-erlang-überwachung} + \end{center} + \item M wartet auf EXIT Nachricht von S $\rightarrow$ asynchroner Handler nötig \end{itemize*} - \item At-most-once: - \begin{itemize*} - \item garantiert, dass mehrfache Aufrufe nur zu einziger Ausführung führen - \item z.B. durch Sequenznummern (erfordert Protokollierung zur Duplikateliminierung) - \item für nicht-idempotente Operationen (schreibend, z.B. Einfügen, Löschen) - \end{itemize*} -\end{itemize*} - -\subsubsection{Überwachung von Erlang-Prozessen} -\begin{itemize*} - \item Linking von Prozessen: \color{green}link\color{blue}(Pid) \color{black} - \item M überwacht S; S bricht durch Fehler ab - \begin{center} - \centering - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-erlang-überwachung} - \end{center} - \item M wartet auf EXIT Nachricht von S $\rightarrow$ asynchroner Handler nötig -\end{itemize*} - -\subsubsection{on\_exit-Handler} -\begin{lstlisting}[language=erlang] + + \subsubsection{on\_exit-Handler} + \begin{lstlisting}[language=erlang] on_exit(Pid, Fun) -> spawn(fun() -> process_flag(trap_exit, true), @@ -5769,80 +5772,80 @@ on_exit(Pid, Fun) -> end end). \end{lstlisting} - -\begin{itemize*} - \item überwacht den Prozess \textbf{Pid} auf Abbruch - \item Anwendungsspezifische Reaktionen möglich + \begin{itemize*} - \item Fehlermeldung - \item Neustart des Prozesses + \item überwacht den Prozess \textbf{Pid} auf Abbruch + \item Anwendungsspezifische Reaktionen möglich + \begin{itemize*} + \item Fehlermeldung + \item Neustart des Prozesses + \end{itemize*} + \item auch über Erlang-Knotengrenzen hinweg! \end{itemize*} - \item auch über Erlang-Knotengrenzen hinweg! -\end{itemize*} - -\paragraph{Anwendung des on\_exit-Handlers} -\begin{lstlisting}[language=erlang] + + \paragraph{Anwendung des on\_exit-Handlers} + \begin{lstlisting}[language=erlang] F = fun() -> receive X -> list_to_atom(X) end end. Pid = spawn(F). on_exit(Pid, fun(Why) -> io:format("~p died with ~p~n", [Pid, Why]) end). Pid ! ping. \end{lstlisting} - -\begin{itemize*} - \item Funktion anlegen (Liste in Atom konvertieren) - \item Prozess erzeugen - \item \textbf{on\_exit}-Handler definieren - \item Fehler verursachen (Nachricht ist keine Liste) -\end{itemize*} - -\subsubsection{Fehlersemantiken} -Umgang mit Fehlern (Timeouts, Ausfälle) -\begin{itemize*} - \item \color{orange} Maybe: \color{black} + \begin{itemize*} - \item keine Wiederholung - \item keine Ausführungsgarantie + \item Funktion anlegen (Liste in Atom konvertieren) + \item Prozess erzeugen + \item \textbf{on\_exit}-Handler definieren + \item Fehler verursachen (Nachricht ist keine Liste) \end{itemize*} - \item \color{orange} At-least-once: \color{black} + + \subsubsection{Fehlersemantiken} + Umgang mit Fehlern (Timeouts, Ausfälle) \begin{itemize*} - \item wiederholte Ausführung, aber keine Erkennung von Nachrichtenduplikaten - \item nur für idempotente Optionen (Lesen) + \item \color{orange} Maybe: \color{black} + \begin{itemize*} + \item keine Wiederholung + \item keine Ausführungsgarantie + \end{itemize*} + \item \color{orange} At-least-once: \color{black} + \begin{itemize*} + \item wiederholte Ausführung, aber keine Erkennung von Nachrichtenduplikaten + \item nur für idempotente Optionen (Lesen) + \end{itemize*} + \item \color{orange} At-most-once: \color{black} + \begin{itemize*} + \item garantiert, dass mehrfache Aufrufe nur zu einziger Ausführung führen + \item z.B. durch Sequenznummern (erfordert Protokollierung zur Duplikatelliminierung) + \item für nicht-idempotente Operationen (schreibend, z.B. Einfügen, Löschen) + \end{itemize*} \end{itemize*} - \item \color{orange} At-most-once: \color{black} + + \subsubsection{Auftragsorientierte Modelle} \begin{itemize*} - \item garantiert, dass mehrfache Aufrufe nur zu einziger Ausführung führen - \item z.B. durch Sequenznummern (erfordert Protokollierung zur Duplikatelliminierung) - \item für nicht-idempotente Operationen (schreibend, z.B. Einfügen, Löschen) + \item klassische Modell serviceorientierten Systemdesigns + \item in verteilten Systemen: + \begin{itemize*} + \item Menge von Dienstanbietern (Server) + \item Menge von Clients, die diese Dienste nutzen wollen + \end{itemize*} \end{itemize*} -\end{itemize*} - -\subsubsection{Auftragsorientierte Modelle} -\begin{itemize*} - \item klassische Modell serviceorientierten Systemdesigns - \item in verteilten Systemen: + Typische Anwendungsszenarien + \begin{description*} + \item[DB-Server] verwalten Datenbestände, verarbeiten SQL Anfragen + \begin{itemize*} + \item Clients: 'Gib mir alle Personen, die älter als 18 Jahre alt sind' + \end{itemize*} + \item[Web] Webserver stellt HTML Dokumente bereit, Browser ruft URLs für Dokumente auf + \item[E-Mail] Mailserver verwalten Postfächer, leiten Mails weiter, Outlook/Thunderbird/...senden/lesen von Emails + \item[Namensdienste (DNS), Fileserver, Zeitserver (NTP)] + \end{description*} + + \paragraph{Auftragsorientierte Modelle: Modellsicht} + \begin{itemize*} - \item Menge von Dienstanbietern (Server) - \item Menge von Clients, die diese Dienste nutzen wollen + \item Rollenmodell: Clients erteilen Aufträge an Server + \item Datenmodell: Notschaften mit vereinbarter Struktur (Protokoll) \end{itemize*} -\end{itemize*} -Typische Anwendungsszenarien -\begin{description*} - \item[DB-Server] verwalten Datenbestände, verarbeiten SQL Anfragen - \begin{itemize*} - \item Clients: 'Gib mir alle Personen, die älter als 18 Jahre alt sind' - \end{itemize*} - \item[Web] Webserver stellt HTML Dokumente bereit, Browser ruft URLs für Dokumente auf - \item[E-Mail] Mailserver verwalten Postfächer, leiten Mails weiter, Outlook/Thunderbird/...senden/lesen von Emails - \item[Namensdienste (DNS), Fileserver, Zeitserver (NTP)] -\end{description*} - -\paragraph{Auftragsorientierte Modelle: Modellsicht} - -\begin{itemize*} - \item Rollenmodell: Clients erteilen Aufträge an Server - \item Datenmodell: Notschaften mit vereinbarter Struktur (Protokoll) -\end{itemize*} -\begin{lstlisting} + \begin{lstlisting} POST /axis2/services/TimeWS HTTP/1.1 Content-Type: application/soap+xml; charset=UTF-8; action="urn:getTimeOfDay" @@ -5852,33 +5855,33 @@ action="urn:getTimeOfDay" \end{lstlisting} - -\begin{itemize*} - \item Fehlersemantiken: Was ist der Grund, wenn ich keine Antwort erhalte? + \begin{itemize*} - \item Auftrag angekommen? Vollständig bearbeitet? - \item Was passiert wenn ein Auftrag wiederholt wird? + \item Fehlersemantiken: Was ist der Grund, wenn ich keine Antwort erhalte? + \begin{itemize*} + \item Auftrag angekommen? Vollständig bearbeitet? + \item Was passiert wenn ein Auftrag wiederholt wird? + \end{itemize*} + \item Terminierungssemantiken: + \begin{itemize*} + \item Auftragserteilung in der Regel synchron + \item es existieren aber auch asynchrone Aufträge + \end{itemize*} \end{itemize*} - \item Terminierungssemantiken: + + \paragraph{Auftragsorientierte Modelle: Implementierung} + \begin{itemize*} - \item Auftragserteilung in der Regel synchron - \item es existieren aber auch asynchrone Aufträge + \item Implementierung aufbauend auf send/receive \end{itemize*} -\end{itemize*} - -\paragraph{Auftragsorientierte Modelle: Implementierung} - -\begin{itemize*} - \item Implementierung aufbauend auf send/receive -\end{itemize*} -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-kommunikation} -\end{center} \ \linebreak - -\paragraph{Ein Fileserver in Java} - -Server -\begin{lstlisting}[language=java] + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-kommunikation} + \end{center} \ \linebreak + + \paragraph{Ein Fileserver in Java} + + Server + \begin{lstlisting}[language=java] try(ServerSocker ss = new ServerSocket(4242)){ Socket s = ss.accept(); //warte auf Clients // ... @@ -5897,16 +5900,16 @@ try(ServerSocker ss = new ServerSocket(4242)){ // ... } /*switch*/ } /*if*/ } /*try*/ \end{lstlisting} -Erläuterungen zum Server -\begin{itemize*} - \item Zeile 1 \& 2: Serversocket erstellen, lauscht auf Port 4242, wartet blockierend bis sich ein Client verbindet - \item Zeile 6: lies eine Zeile vom Client - \item Zeile 7: unser Nachrichtenformat: \textbf{Operation $<$Leerzeichen$>$ Dateipfad} - \item Zeile 8ff: unterscheide Operationen und führe Aktionen aus; antworte dem Client entsprechend -\end{itemize*} - -Client -\begin{lstlisting}[language=java] + Erläuterungen zum Server + \begin{itemize*} + \item Zeile 1 \& 2: Serversocket erstellen, lauscht auf Port 4242, wartet blockierend bis sich ein Client verbindet + \item Zeile 6: lies eine Zeile vom Client + \item Zeile 7: unser Nachrichtenformat: \textbf{Operation $<$Leerzeichen$>$ Dateipfad} + \item Zeile 8ff: unterscheide Operationen und führe Aktionen aus; antworte dem Client entsprechend + \end{itemize*} + + Client + \begin{lstlisting}[language=java] try(Socket socket = new Socker("localhost", 4242)){ String command = args[0] + " " + args[1]; @@ -5918,133 +5921,133 @@ try(Socket socket = new Socker("localhost", 4242)){ } } \end{lstlisting} -\begin{itemize*} - \item Zeile 1: erstelle Clientsocket, d.h. Verbindungsaufbau zum Server auf localhost auf Port 4242 - \item Zeile 2: lese Befehl und Dateipfad - \item Zeile 4: sende Befehl als String an den Server - \item Zeile 6ff: lese alle Antwortzeilen vom Server; Ausgabe auf dem Bildschirm -\end{itemize*} - -\paragraph{Auftragsorientierte Modelle} - -\begin{itemize*} - \item Können benutzt werden, um einfache Protokolle zu implementieren - \item Binär oder ASCII \begin{itemize*} - \item auch Übertragung komplexer Objekte möglich + \item Zeile 1: erstelle Clientsocket, d.h. Verbindungsaufbau zum Server auf localhost auf Port 4242 + \item Zeile 2: lese Befehl und Dateipfad + \item Zeile 4: sende Befehl als String an den Server + \item Zeile 6ff: lese alle Antwortzeilen vom Server; Ausgabe auf dem Bildschirm \end{itemize*} - \item gesendeter Befehl könnte einer Methode/Funktion auf dem Server entsprechen + + \paragraph{Auftragsorientierte Modelle} + \begin{itemize*} - \item es erfolgt eine Art entfernter Funktionsaufruf - \item RPC wird im nächsten Abschnitt behandelt - \end{itemize*} - \item Funktionalität kann über das Internet angeboten werden - \begin{itemize*} - \item[$\Rightarrow$] Implementierung eines Webservices - \end{itemize*} -\end{itemize*} - -\subsubsection{Webservices - Allgemein} -\begin{itemize*} - \item WebService: Dienst, der über das Internet/WWW von Clients angesprochen werden kann - \item typischerweise über HTTP - \item Früher \textbf{SOAP}: Simple Object Access Protocol - \begin{itemize*} - \item Protokoll zum Austausch von Informationen in XML - \item Verzeichnisdienste zum Finden von Diensten, z.B. UDDI - \end{itemize*} - \item Heute \textbf{REST} -\end{itemize*} - -\subsubsection{REST} -\begin{itemize*} - \item Die Grundidee von REST: - \begin{itemize*} - \item \textbf{REST}: Representional State Transfer - \item oftmals existiert ein HTTP Server / Anwendungsserver schon - \item Idee: Jede Ressource die vom Server angeboten wird, ist durch eine URI beschrieben/identifiziert + \item Können benutzt werden, um einfache Protokolle zu implementieren + \item Binär oder ASCII \begin{itemize*} - \item Datei, ein Eintrag in einer Datenbank, Tweet,... + \item auch Übertragung komplexer Objekte möglich \end{itemize*} - \item Anlegen, Lesen, Verändern, Löschen (CRUD) + \item gesendeter Befehl könnte einer Methode/Funktion auf dem Server entsprechen \begin{itemize*} - \item Art der Operation über HTTP Request-Typ festlegen (POST, GET, PUT, DELETE) + \item es erfolgt eine Art entfernter Funktionsaufruf + \item RPC wird im nächsten Abschnitt behandelt \end{itemize*} - \item Unabhängigkeit von verwendeter Programmiersprache in Client und Server durch HTTP und Textformate - \end{itemize*} -\end{itemize*} - -\paragraph{Anforderungen an Ressourcen} - -Anforderungen an Ressourcen nach Fielding: -\begin{enumerate*} - \item Adressierbarkeit: jede Ressource muss über URI adressierbar sein (Achtung: URI != URL, Identifier vs. Locator) - \item Zustandslosigkeit: Kommunikation zwischen Client und Server hat keinen Zustand (Session/Cookie) - \begin{itemize*} - \item bei jeder Anfrage werden alle Informationen gesendet - \end{itemize*} - \item Einheitliche Schnittstelle: über HTTP Standardmethoden auf Ressourcen zugreifen - \item Entkopplung von Ressource und Repräsentation: Ressourcen können in verschiedenen Formaten angeboten werden (JSON, XML,...) -\end{enumerate*} - -\paragraph{HTTP Methoden für REST} - -\begin{itemize*} - \item selbe URL mit verschiedenen Methoden aufrufbar - \item Methode bestimmt ausgeführte Aktion auf dem Server -\end{itemize*} -\begin{description*} - \item[GET] eine Ressource lese, Daten sollten nicht verändert werden - \item[POST] neue Ressource erstellen - \begin{itemize*} - \item Die URI ist dem Anrufer zunächst unbekannt - \item Der Server kann dem Anrufer die erzeugte URI in der Antwort mitteilen - \end{itemize*} - \item[PUT] neue Ressource erstellen, oder existierende bearbeiten - \item[DELETE] zum Löschen von Ressourcen -\end{description*} - -\paragraph{REST - Beispiel} - -Spotify API -\begin{itemize*} - \item \textbf{Authorization}-Header benötigt - \item \textbf{id}: Spotify-ID eines Künstlers -\end{itemize*} -\begin{center} - \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-spotify-api} -\end{center} - -\paragraph{Implementierung von RESTful Webservices} - -\begin{itemize*} - \item manuelle Implementierung recht aufwändig - \begin{itemize*} - \item unterscheiden von HTTP Methoden (GET, POST,...) - \item parsen/prüfen von URL Pfaden und Parametern - \item setzen von Antwortheadern \& Kodierung in XML/JSON - \end{itemize*} - \item REST Frameworks erleichtern die Arbeit deutlich - \begin{itemize*} - \item JAX-RS Spezifikation für Java zur Erstellung von RESTful Services + \item Funktionalität kann über das Internet angeboten werden \begin{itemize*} - \item Implementierung: Jersey: https://eclipse-ee4j.github.io/jersey/ - \item Implementierung: Spring: - https://spring.io/guides/gs/rest-service/ + \item[$\Rightarrow$] Implementierung eines Webservices \end{itemize*} - \item Microsofts \textbf{cpprestsdk} für C++ als Client-Bibliothek: - https://github.com/Microsoft/cpprestsdk \end{itemize*} -\end{itemize*} -\begin{itemize*} - \item Beispiel: Jersey - \item Definition einer einfachen Klasse + + \subsubsection{Webservices - Allgemein} \begin{itemize*} - \item Einstellungen über Annotationen - \item Klasse muss als Servlet in einem Applicationserver ausgeführt werden + \item WebService: Dienst, der über das Internet/WWW von Clients angesprochen werden kann + \item typischerweise über HTTP + \item Früher \textbf{SOAP}: Simple Object Access Protocol + \begin{itemize*} + \item Protokoll zum Austausch von Informationen in XML + \item Verzeichnisdienste zum Finden von Diensten, z.B. UDDI + \end{itemize*} + \item Heute \textbf{REST} \end{itemize*} -\end{itemize*} -\begin{lstlisting} + + \subsubsection{REST} + \begin{itemize*} + \item Die Grundidee von REST: + \begin{itemize*} + \item \textbf{REST}: Representional State Transfer + \item oftmals existiert ein HTTP Server / Anwendungsserver schon + \item Idee: Jede Ressource die vom Server angeboten wird, ist durch eine URI beschrieben/identifiziert + \begin{itemize*} + \item Datei, ein Eintrag in einer Datenbank, Tweet,... + \end{itemize*} + \item Anlegen, Lesen, Verändern, Löschen (CRUD) + \begin{itemize*} + \item Art der Operation über HTTP Request-Typ festlegen (POST, GET, PUT, DELETE) + \end{itemize*} + \item Unabhängigkeit von verwendeter Programmiersprache in Client und Server durch HTTP und Textformate + \end{itemize*} + \end{itemize*} + + \paragraph{Anforderungen an Ressourcen} + + Anforderungen an Ressourcen nach Fielding: + \begin{enumerate*} + \item Adressierbarkeit: jede Ressource muss über URI adressierbar sein (Achtung: URI != URL, Identifier vs. Locator) + \item Zustandslosigkeit: Kommunikation zwischen Client und Server hat keinen Zustand (Session/Cookie) + \begin{itemize*} + \item bei jeder Anfrage werden alle Informationen gesendet + \end{itemize*} + \item Einheitliche Schnittstelle: über HTTP Standardmethoden auf Ressourcen zugreifen + \item Entkopplung von Ressource und Repräsentation: Ressourcen können in verschiedenen Formaten angeboten werden (JSON, XML,...) + \end{enumerate*} + + \paragraph{HTTP Methoden für REST} + + \begin{itemize*} + \item selbe URL mit verschiedenen Methoden aufrufbar + \item Methode bestimmt ausgeführte Aktion auf dem Server + \end{itemize*} + \begin{description*} + \item[GET] eine Ressource lese, Daten sollten nicht verändert werden + \item[POST] neue Ressource erstellen + \begin{itemize*} + \item Die URI ist dem Anrufer zunächst unbekannt + \item Der Server kann dem Anrufer die erzeugte URI in der Antwort mitteilen + \end{itemize*} + \item[PUT] neue Ressource erstellen, oder existierende bearbeiten + \item[DELETE] zum Löschen von Ressourcen + \end{description*} + + \paragraph{REST - Beispiel} + + Spotify API + \begin{itemize*} + \item \textbf{Authorization}-Header benötigt + \item \textbf{id}: Spotify-ID eines Künstlers + \end{itemize*} + \begin{center} + \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-spotify-api} + \end{center} + + \paragraph{Implementierung von RESTful Webservices} + + \begin{itemize*} + \item manuelle Implementierung recht aufwändig + \begin{itemize*} + \item unterscheiden von HTTP Methoden (GET, POST,...) + \item parsen/prüfen von URL Pfaden und Parametern + \item setzen von Antwortheadern \& Kodierung in XML/JSON + \end{itemize*} + \item REST Frameworks erleichtern die Arbeit deutlich + \begin{itemize*} + \item JAX-RS Spezifikation für Java zur Erstellung von RESTful Services + \begin{itemize*} + \item Implementierung: Jersey: https://eclipse-ee4j.github.io/jersey/ + \item Implementierung: Spring: + https://spring.io/guides/gs/rest-service/ + \end{itemize*} + \item Microsofts \textbf{cpprestsdk} für C++ als Client-Bibliothek: + https://github.com/Microsoft/cpprestsdk + \end{itemize*} + \end{itemize*} + \begin{itemize*} + \item Beispiel: Jersey + \item Definition einer einfachen Klasse + \begin{itemize*} + \item Einstellungen über Annotationen + \item Klasse muss als Servlet in einem Applicationserver ausgeführt werden + \end{itemize*} + \end{itemize*} + \begin{lstlisting} @Path("/files") public class FileServer { @GET @@ -6056,45 +6059,45 @@ public class FileServer { } } \end{lstlisting} - -\paragraph{Restful Webservice - Erläuterungen} - -\begin{itemize*} - \item Zeile 1: dieser Dienst ist über den Pfad files erreichbar, - z.B. http://localhost/files - \item Zeile 3: die nachfolgende Methode soll HTTP GET Anfragen - verarbeiten - \item Zeile 4: die URL enthält den Dateinamen als - Pfad-Bestandteil, z.B. - http://localhost/files/myfile.txt - \item Zeile 5: Hinweis an das Jersey-Framework das Ergebnis - automatisch ins JSON Format umzuwandeln - \item Zeile 6: normale Definition einer Methode \& Mapping des - Eingabeparameters auf den URL-Parameter - \item Zeile 8: das infos Objekt vom Typ FileInfo wird - automatisch als JSON repräsentiert -\end{itemize*} - -\paragraph{Aufruf von REST-Services} - -https://reques.in kostenloser Dienst zum Testen von REST-Clients -\begin{itemize*} - \item Variante 1: telnet reques.in 80 ... - \item Variante 2: Auf der Kommandozeile \newline - \$ curl https://reqres.in/api/users/1 - \begin{lstlisting}[language=java] + + \paragraph{Restful Webservice - Erläuterungen} + + \begin{itemize*} + \item Zeile 1: dieser Dienst ist über den Pfad files erreichbar, + z.B. http://localhost/files + \item Zeile 3: die nachfolgende Methode soll HTTP GET Anfragen + verarbeiten + \item Zeile 4: die URL enthält den Dateinamen als + Pfad-Bestandteil, z.B. + http://localhost/files/myfile.txt + \item Zeile 5: Hinweis an das Jersey-Framework das Ergebnis + automatisch ins JSON Format umzuwandeln + \item Zeile 6: normale Definition einer Methode \& Mapping des + Eingabeparameters auf den URL-Parameter + \item Zeile 8: das infos Objekt vom Typ FileInfo wird + automatisch als JSON repräsentiert + \end{itemize*} + + \paragraph{Aufruf von REST-Services} + + https://reques.in kostenloser Dienst zum Testen von REST-Clients + \begin{itemize*} + \item Variante 1: telnet reques.in 80 ... + \item Variante 2: Auf der Kommandozeile \newline + \$ curl https://reqres.in/api/users/1 + \begin{lstlisting}[language=java] {"data":{ "id":1, "email":"abc.def@xyz.de", "first_name": "ABC", "last_name":"DEF", ... }} \end{lstlisting} - \item Variante 3: Aufruf in einem Programm -\end{itemize*} - -\paragraph{HTTP GET Aufrufe in Java} - -In Java ab Version 11 eingebauter HTTP Client -\begin{lstlisting}[language=java] + \item Variante 3: Aufruf in einem Programm + \end{itemize*} + + \paragraph{HTTP GET Aufrufe in Java} + + In Java ab Version 11 eingebauter HTTP Client + \begin{lstlisting}[language=java] HttpClient httpCliet = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() @@ -6108,9 +6111,9 @@ HttpResponse response = httpClient.send( System.out.println(response.body()); \end{lstlisting} - -\paragraph{HTTP POST in Java} -\begin{lstlisting}[language=java] + + \paragraph{HTTP POST in Java} + \begin{lstlisting}[language=java] String data ="{ \"name\":\"morpheus\",\"job\":\"leader\"}"; HttpRequest postRequest = HttpRequest.newBuilder() .uri(URI.create("https://reqres.in/api/users")) @@ -6122,47 +6125,47 @@ HttpResponse postResp = httpClient.send( ); System.out.println(postResp.body()); \end{lstlisting} -Antwort: -\begin{lstlisting}[language=java] + Antwort: + \begin{lstlisting}[language=java] {"name":"morpheus", "job":"leader", "id":"703", "createdAt":"2020-06-24T12:09:22.148Z"} \end{lstlisting} -Eigentlich: JSON Ergebnis mit geeigneten Frameworks parsen und weiterverarbeiten - -\paragraph{Zusammenfassung} - -\begin{itemize*} - \item Auftragsorientierte Modelle nach dem Client-Server Prinzip - \item WebServices bieten Dienste über das WWW an - \item RESTful WebServices + Eigentlich: JSON Ergebnis mit geeigneten Frameworks parsen und weiterverarbeiten + + \paragraph{Zusammenfassung} + \begin{itemize*} - \item jede Ressource hat eine URI - \item HTTP Methoden für Aktionen auf Ressourcen - \item unabhängig von Programmiersprachen + \item Auftragsorientierte Modelle nach dem Client-Server Prinzip + \item WebServices bieten Dienste über das WWW an + \item RESTful WebServices + \begin{itemize*} + \item jede Ressource hat eine URI + \item HTTP Methoden für Aktionen auf Ressourcen + \item unabhängig von Programmiersprachen + \end{itemize*} \end{itemize*} -\end{itemize*} - -\subsubsection{Funktionsaufrufbasierte Protokolle} -\begin{itemize*} - \item Grundidee: Adaption von anwendungsnahen und unkomplizierten Kommunikationsparadigmen an Eigenschaften verteilter Systeme - \item d.h., aus Aufrufen auf lokalen Prozeduren und Methoden werden Aufrufe entfernter Prozeduren und Methoden - \item bekannt als: + + \subsubsection{Funktionsaufrufbasierte Protokolle} \begin{itemize*} - \item RPC: Remote Procedure Calls - \item oder Java RMI: Remote Method Invocation + \item Grundidee: Adaption von anwendungsnahen und unkomplizierten Kommunikationsparadigmen an Eigenschaften verteilter Systeme + \item d.h., aus Aufrufen auf lokalen Prozeduren und Methoden werden Aufrufe entfernter Prozeduren und Methoden + \item bekannt als: + \begin{itemize*} + \item RPC: Remote Procedure Calls + \item oder Java RMI: Remote Method Invocation + \end{itemize*} + \item Erlang und Java haben die Konzepte nativ implementiert, in C++ nur über zusätzliche Bibliotheken \end{itemize*} - \item Erlang und Java haben die Konzepte nativ implementiert, in C++ nur über zusätzliche Bibliotheken -\end{itemize*} - -\paragraph{Eigenschaften von Prozedurfernaufrufen} - -Aufruf und Ausführung in unterschiedlichen Umgebungen/Kontexten -\begin{itemize*} - \item Programmiersprachen - \item Namens- und Adressräume - \item Betriebssystemkontext - \item Hardwarekontext -\end{itemize*} -Woher kennt der Aufrufer die Signatur der Prozedur auf dem Server? $\Rightarrow$ \color{orange} Stubs \color{black} + + \paragraph{Eigenschaften von Prozedurfernaufrufen} + + Aufruf und Ausführung in unterschiedlichen Umgebungen/Kontexten + \begin{itemize*} + \item Programmiersprachen + \item Namens- und Adressräume + \item Betriebssystemkontext + \item Hardwarekontext + \end{itemize*} + Woher kennt der Aufrufer die Signatur der Prozedur auf dem Server? $\Rightarrow$ \color{orange} Stubs \color{black} \begin{center} \includegraphics[width=0.4\linewidth]{Assets/Programmierparadigmen-netzwerk-stubs} \end{center} @@ -6775,8 +6778,8 @@ public class HandlerUserDetails implements RequestHandler { } /* method */ } /* class */ \end{lstlisting} \begin{description*} -\item[RequestHandler] als Einstiegspunkt für Lambda-Funktionen -\item[handleRequest] wird von der Lambda-Umgebung aufgerufen + \item[RequestHandler] als Einstiegspunkt für Lambda-Funktionen + \item[handleRequest] wird von der Lambda-Umgebung aufgerufen \end{description*} \subsubsection{Spring Functions}