Programación

1º DAM/DAW - Curso 2021-2022

User Tools

Site Tools


apuntes:concurrencia

This is an old revision of the document!


Concurrencia

Programación multihilo

\part*{Programación concurrente}

\section{Introducción a la programación multihilo}

\subsection{Multiproceso}

El multiproceso consiste en la ejecución de varios procesos diferentes de forma simultánea para la realización de una o varias tareas relacionadas o no entre sí. En este caso, cada uno de estos procesos es una aplicación independiente. El caso más conocido es aquel en el que nos referimos al Sistema Operativo (Windows, Linux, MacOS, . . .) y decimos que es multitarea puesto que es capaz de ejecutar varias tareas o procesos (o programas) al mismo tiempo.

\subsection{Multihilo}

Hablamos de multihilo cuando se ejecutan varias tareas relacionadas o no entre sí dentro de una misma aplicación. En este caso no son procesos diferentes sino que dichas tareas se ejecutan dentro del mismo proceso del Sistema Operativo. A cada una de estas tareas se le conoce como hilo o thread (en algunos contextos también como procesos ligeros).

En ambos casos estaríamos hablando de lo que se conoce como Programación Concurrente. Hay que tener en cuenta que en ninguno de los dos casos la ejecución es realmente simultánea, ya que el Sistema Operativo es quién hace que parezca así, pero los ejecuta siguiendo lo que se conoce como algoritmos de planificación.

\subsection{Algoritmos de planificación}

En entornos multitarea, un algoritmo de planificación indica la forma en que el tiempo de procesamiento debe repartirse entre todas las tareas que deben ejecutarse en un momento determinado. Existen diferentes algoritmos de planificación, cada uno con sus ventajas e inconvenientes, pero todos intentan cumplir con los siguientes puntos:

\begin{itemize}

  \item Debe ser imparcial y eficiente
  \item Debe minimizar el tiempo de respuesta al usuario, sobre todo en aquellos procesos o tareas más interactivas
  \item Debe ejecutar el mayor número de procesos
  \item Debe mantener un equilibrio en el uso de los recursos del sistema

\end{itemize}

\subsection{Programación concurrente, paralela y distribuida}

\subsubsection*{Programación concurrente}

Es la programación de aplicaciones capaces de realizar varias tareas de forma simultánea utilizando hilos o threads. En este caso todas las tareas compiten por el uso del procesador (lo más habitual es disponer sólo de uno) y en un instante determinado sólo una de ellas se encuentra en ejecución. Además, habrá que tener en cuenta que diferentes hilos pueden compartir información entre sí y eso complica mucho su programación y coordinación.

\subsubsection*{Programación paralela}

Es la programación de aplicaciones que ejecutan tareas de forma paralela, de forma que no compiten por el procesador puesto que cada una de ellas se ejecuta en uno diferente. Normalmente buscan resultados comunes dividiendo el problema en varias tareas que se ejecutan al mismo tiempo.

\subsubsection*{Programación distribuida}

Es la programación de aplicaciones en las que las tareas a ejecutar se reparten entre varios equipos diferentes (conectados en red, a los que llamaremos nodos). Juntos, estos equipos, forman lo que se conoce como un Sistema Distribuido, que busca formar redes de equipos que trabajen con un fin común

\begin{figure}[h!]

  \centering
  \includegraphics[scale=0.8]{partes/varios/concurrencia.jpg}
  \caption{Programación concurrente/paralela}

\end{figure}

\begin{figure}[h!]

  \centering
  \includegraphics[scale=1]{partes/varios/distribuida.jpg}
  \caption{Programación distribuida}

\end{figure}

\subsection{¿Qué son los hilos?}

Un hilo o thread es cada una de las tareas que puede realizar de forma simultánea una aplicación. Por defecto, toda aplicación dispone de un único hilo de ejecución, al que se conoce como hilo principal. Si dicha aplicación no despliega ningún otro hilo, sólo será capaz de ejecutar una tarea al mismo tiempo en ese hilo principal.

Así, para cada tarea adicional que se quiera ejecutar en esa aplicación, se deberá lanzar un nuevo hilo o thread. Para ello, todos los lenguajes de programación, como Java, disponen de una API para crear y trabajar con ellos.

En cualquier caso, es muy importante conocer los estados en los que se pueden encontrar un hilo. Estos estados se suelen representar mediante un gráfico como el que sigue:

\subsubsection*{Estados de un hilo}

\begin{figure}[h!]

  \centering
  \includegraphics[scale=1.4]{partes/varios/estados_hilo.png}
  \caption{Estados de un hilo}

\end{figure}

\section{Programación multihilo en Java}

\subsection{Creación de un hilo}

Para la creación de hilos en Java disponemos de varias vías, combinando el uso de la clase Thread y el interface Runnable según nos interese:

\begin{itemize}

  \item Podemos utiliza la clase Thread heredando de ella. Es quizás la forma más cómoda porque una clase que hereda de Thread se convierte automáticamente en un hilo. Tiene una pega: esa clase ya no podrá heredera de ninguna otra, por lo que si la arquitectura de nuestra aplicación lo requiere ya no podríamos.
  \item Si tenemos la limitación que acabamos de comentar para el primer caso, podemos implementar el interface Runnable de forma que la clase que nosotros estamos implementado podrá además heredar sin ninguna limitación. Sólo cambia un poco la forma de trabajar directamente con la clase hilo.
  \item Por otra parte también podemos crear un hilo utilizando una clase anónima. No es un método que se recomiende pero en algunos casos, cuando la clase que hace de hilo no va a tener una estructura concreta es bastante cómodo hacerlo de esta manera.

\end{itemize}

\subsubsection*{Crear un hilo heredando de la clase Thread}

\begin{lstlisting}[language=java] public class Tarea extends Thread {

@Override
public void run() {
  for (int i = 0; i < 10; i++) {
    System.out.println("Soy un hilo y esto es lo que hago");
  }
}

}

. . .

public class Programa {

public static void main(String args[]) {
  Tarea tarea = new Tarea();
  tarea.start();
  System.out.println("Yo soy el hilo principal y sigo haciendo mi trabajo");
  System.out.println("Fin del hilo principal");
}

} \end{lstlisting}

\subsubsection*{Crear un hilo implementando la interfaz Runnable}

\begin{lstlisting}[language=java] public class OtraClase {

. . .
. . .

}

. . .

public class Tarea extends OtraClase implements Runnable {

@Override
public void run() {
  for (int i = 0; i < 10; i++) {
    System.out.println("Soy un hilo y esto es lo que hago");
  }
}

}

. . .

public class Programa {

public static void main(String args[]) {
  Tarea tarea = new Tarea();
  Thread hilo = new Thread(tarea);
  hilo.start();
  System.out.println("Yo soy el hilo principal y sigo haciendo mi trabajo");
  System.out.println("Fin del hilo principal");
}

} \end{lstlisting}

\subsubsection*{Crear un hilo implementando una clase anónima/expresión lambda}

\begin{lstlisting}[language=java] public class Programa {

public static void main(String args[]) {

Thread hilo = new Thread(new Runnable() {

    @Override
    public void run() {
      for (int i = 0; i < 10; i++) {
      System.out.println("Soy un hilo y esto es lo que hago");
    }
  });

hilo.start();

  System.out.println("Yo soy el hilo principal y sigo haciendo mi trabajo");
  System.out.println("Fin del hilo principal");
}

} \end{lstlisting}

En cualquier caso tenemos que tener siempre en cuenta las siguientes consideraciones:

\begin{itemize}

  \item Siempre se debe sobreescribir (Override) el método run() e implementar allí lo que tiene que hacer el hilo
  \item Podemos hacer que el hilo haga un número finito de cosas o bien que esté siempre en segundo plano (tendremos que asegurar que el método run() se ejecuta de forma continuada)(¿cómo se hace eso?)
  \item Los problemas vienen cuando existen varios hilos. Hay que tener en cuenta que pueden compartir datos y código y encontrarse en diferentes estados de ejecución
  \item La ejecución de nuestra aplicación será thread-safe si se puede garantizar una correcta manipulación de los datos que comparten los hilos de la aplicación sin resultados inesperados (más adelante veremos cómo)
  \item Además, en el caso de aplicaciones multihilo, también nos puede interesar sincronizar y comunicar unos hilos con otros

\end{itemize}

También resulta interesante saber cómo detener un hilo. En este caso, la API de Java desaconsejó el método stop() que en un principio ideó para detener la ejecución. Así, hoy en día, se nos anima a que seamos nosotros quienes implementemos formas limpias de detener nuestros hilos.

\subsection{Sincronización de hilos}

El API de Java proporciona una serie de métodos en la clase Thread para la sincronización de los hilos en una aplicación:

\begin{itemize}

  \item \verb join()  Se espera la terminación del hilo que invoca a este método antes de continuar
  \item \verb Thread.sleep(int)  El hilo que ejecuta esta llamada permanece dormido durante el tiempo especificado como parámetro (en ms)
  \item \verb isAlive()  Comprueba si el hilo permanece activo todavía (no ha terminado su ejecución)
  \item \verb yield()  Sugiere al scheduler que sea otro hilo el que se ejecute (no se asegura)

\end{itemize}

\begin{lstlisting}[language=java] public static void main(String args[]) {

Hilo hilo1 = new Thread(new Tarea());
Hilo hilo2 = new Thread(new Tarea());

hilo1.start();

hilo2.start();

. . .

. . .

hilo1.join();

hilo2.join();

System.out.println(“Fin de la ejecución de los dos hilos”); } \end{lstlisting}

El hilo principal espera a que ambos hilos se hayan ejecutado para continuar (o para lo que sea)

\begin{lstlisting}[language=java] public static void main(String args[]) {

Hilo hilo1 = new Thread(new Tarea());
Hilo hilo2 = new Thread(new Tarea());

hilo1.start();

hilo1.join();

hilo2.start();

hilo2.join();

System.out.println(“Fin de la ejecución de los dos hilos”); } \end{lstlisting}

En este caso los hilos se ejecutan uno después de otro

\begin{lstlisting}[language=java] public class Tarea implements Runnable {

@Override
public void run() {
  for (int i = 0; i < 10; i++) {
    System.out.println("Soy un hilo y esto es lo que hago");
    try {
      Thread.sleep(500);
    } catch (InterruptedException ie) {
      ie.printStackTrace();
    }
  }
}

} \end{lstlisting}

En este caso el hilo duerme (detiene su ejecución) durante el tiempo especificado (en ms). Durante ese momento podrán ejecutarse otros hilos

\begin{lstlisting}[language=java] public class TareaPrincipal implements Runnable {

@Override

public void run() {
  for (int i = 0; i < 10; i++) {
    System.out.println("Soy la TarePrincipal");
    try {
      Thread.sleep(500);
    } catch (InterruptedException ie) {
      ie.printStackTrace();
    }
  }
}

}

. . .

public class TareaAlive implements Runnable {

private Thread otroHilo;

public TareaAlive(Thread otroHilo) {

  this.otroHilo = otroHilo;
}

@Override

public void run() {
  while (otroHilo.isAlive()) {
    System.out.println("Yo hago cosas mientras el otro hilo siga en ejecución");
    try {
      Thread.sleep(500);
    } catch (InterruptedException ie) {
      ie.printStackTrace();
    }
  }

System.out.println(“El otro hilo ha terminado. Yo también”);

}

}

. . .

public class Programa {

public static void main(String args[]) {
  TarePrincipal tareaPrincipal = new TareaPrincipal();
  Thread hiloPrincipal = new Thread(tareaPrincipal);

TareaAlive tareaAlive = new TareaAlive(hiloPrincipal);

  Thread hiloAlive = new Thread(tareaAlive);

hiloPrincipal.start();

  hiloAlive.start();

System.out.println(“Se han terminado los dos hilos?”);

}

} \end{lstlisting}

\verb isAlive() está indicando que el hilo está vivo (ha iniciado su ejecución y aún no ha muerto, puede estar en cualquier estado intermedio, incluso durmiendo)

apuntes/concurrencia.1615382334.txt.gz · Last modified: 2021/03/10 13:18 by Santiago Faci