diff --git a/Programmierparadigmen.pdf b/Programmierparadigmen.pdf index 02729b1..92f6ffe 100644 Binary files a/Programmierparadigmen.pdf and b/Programmierparadigmen.pdf differ 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}