Busqueda...

Programación Concurrente


Programación Concurrente

Define el tipo de programación donde un programa puede tener diversos módulos que se ejecutan de forma concurrente (al mismo tiempo). Para que esto suceda es necesario definir hilos de ejecución que ejecuten rutinas especificas (subprogramas o acciones nominadas) de un programa en momento de ejecución (es decir, cuando el programa se convierte en proceso). Aunque pueden ser definidos por el usuario a la hora de programar, es el sistema operativo el que gestiona este tipo de tecnología.

Hilo de ejecución
un hilo de ejecución, hebra o subproceso es la unidad de procesamiento más pequeña que puede ser planificada por un sistema operativo.
La creación de un nuevo hilo es una característica que permite a una aplicación realizar varias tareas a la vez (concurrentemente). Los distintos hilos de ejecución comparten una serie de recursos tales como el espacio de memoria, los archivos abiertos, situación de autenticación, etc. Esta técnica permite simplificar el diseño de una aplicación que debe llevar a cabo distintas funciones simultáneamente.
Un hilo es básicamente una tarea que puede ser ejecutada en paralelo con otra tarea.
Los hilos de ejecución que comparten los mismos recursos, sumados a estos recursos, son en conjunto conocidos como un proceso. El hecho de que los hilos de ejecución de un mismo proceso compartan los recursos hace que cualquiera de estos hilos pueda modificar éstos. Cuando un hilo modifica un dato en la memoria, los otros hilos acceden a ese dato modificado inmediatamente.
Lo que es propio de cada hilo es el contador de programa, la pila de ejecución y el estado de la CPU (incluyendo el valor de los registros).
El proceso sigue en ejecución mientras al menos uno de sus hilos de ejecución siga activo. Cuando el proceso finaliza, todos sus hilos de ejecución también han terminado. Asimismo en el momento en el que todos los hilos de ejecución finalizan, el proceso no existe más y todos sus recursos son liberados.
Algunos lenguajes de programación tienen características de diseño expresamente creadas para permitir a los programadores lidiar con hilos de ejecución (como Java o Delphi). Otros (la mayoría) desconocen la existencia de hilos de ejecución y éstos deben ser creados mediante llamadas de biblioteca especiales que dependen del sistema operativo en el que estos lenguajes están siendo utilizados (como es el caso del C y del C++).
Un ejemplo de la utilización de hilos es tener un hilo atento a la interfaz gráfica (iconos, botones, ventanas), mientras otro hilo hace una larga operación internamente. De esta manera el programa responde de manera más ágil a la interacción con el usuario. También pueden ser utilizados por una aplicación servidora para dar servicio a múltiples clientes.

Diferencias entre hilos y procesos
Los hilos se distinguen de los tradicionales procesos en que los procesos son –generalmente– independientes, llevan bastante información de estados, e interactúan sólo a través de mecanismos de comunicación dados por el sistema. Por otra parte, muchos hilos generalmente comparten otros recursos de forma directa. En muchos de los sistemas operativos que dan facilidades a los hilos, es más rápido cambiar de un hilo a otro dentro del mismo proceso, que cambiar de un proceso a otro. Este fenómeno se debe a que los hilos comparten datos y espacios de direcciones, mientras que los procesos, al ser independientes, no lo hacen. Al cambiar de un proceso a otro el sistema operativo (mediante el dispatcher) genera lo que se conoce como overhead, que es tiempo desperdiciado por el procesador para realizar un cambio de contexto (context switch), en este caso pasar del estado de ejecución (running) al estado de espera (waiting) y colocar el nuevo proceso en ejecución. En los hilos, como pertenecen a un mismo proceso, al realizar un cambio de hilo el tiempo perdido es casi despreciable.
Sistemas operativos como Windows NT, OS/2 y Linux (2.5 o superiores) dicen tener hilos "baratos", y procesos "costosos" mientras que en otros sistemas no hay una gran diferencia.
Funcionalidad de los hilos
Al igual que los procesos, los hilos poseen un estado de ejecución y pueden sincronizarse entre ellos para evitar problemas de compartimiento de recursos. Generalmente, cada hilo tiene una tarea específica y determinada, como forma de aumentar la eficiencia del uso del procesador.

Estados de un hilo
Los principales estados de los hilos son: Ejecución, Listo y Bloqueado. No tiene sentido asociar estados de suspensión de hilos ya que es un concepto de proceso. En todo caso, si un proceso está expulsado de la memoria principal (RAM), todos sus hilos deberán estarlo ya que todos comparten el espacio de direcciones del proceso.

Cambio de estados
  • Creación: Cuando se crea un proceso se crea un hilo para ese proceso. Luego, este hilo puede crear otros hilos dentro del mismo proceso, proporcionando un puntero de instrucción y los argumentos del nuevo hilo. El hilo tendrá su propio contexto y su propio espacio de la columna, y pasará al final de los Listos.
  • Bloqueo: Cuando un hilo necesita esperar por un suceso, se bloquea (salvando sus registros de usuario, contador de programa y punteros de pila). Ahora el procesador podrá pasar a ejecutar otro hilo que esté en la final de los Listos mientras el anterior permanece bloqueado.
  • Desbloqueo: Cuando el suceso por el que el hilo se bloqueó se produce, el mismo pasa a la final de los Listos.
  • Terminación: Cuando un hilo finaliza se liberan tanto su contexto como sus columnas.

Ventajas de los hilos contra procesos
Si bien los hilos son generados a partir de la creación de un proceso, podemos decir que un proceso es un hilo de ejecución, conocido como Monohilo. Pero las ventajas de los hilos se dan cuando hablamos de Multihilos, que es cuando un proceso tiene múltiples hilos de ejecución los cuales realizan actividades distintas, que pueden o no ser cooperativas entre sí. Los beneficios de los hilos se derivan de las implicaciones de rendimiento.
  1. Se tarda mucho menos tiempo en crear un hilo nuevo en un proceso existente que en crear un proceso. Algunas investigaciones llevan al resultado que esto es así en un factor de 10.
  2. Se tarda mucho menos en terminar un hilo que un proceso, ya que cuando se elimina un proceso se debe eliminar el BCP1 del mismo, mientras que un hilo se elimina su contexto y pila.
  3. Se tarda mucho menos tiempo en cambiar entre dos hilos de un mismo proceso
  4. Los hilos aumentan la eficiencia de la comunicación entre programas en ejecución. En la mayoría de los sistemas en la comunicación entre procesos debe intervenir el núcleo para ofrecer protección de los recursos y realizar la comunicación misma. En cambio, entre hilos pueden comunicarse entre sí sin la invocación al núcleo. Por lo tanto, si hay una aplicación que debe implementarse como un conjunto de unidades de ejecución relacionadas, es más eficiente hacerlo con una colección de hilos que con una colección de procesos separados.

Sincronización de hilos
Todos los hilos comparten el mismo espacio de direcciones y otros recursos como pueden ser archivos abiertos. Cualquier modificación de un recurso desde un hilo afecta al entorno del resto de los hilos del mismo proceso. Por lo tanto, es necesario sincronizar la actividad de los distintos hilos para que no interfieran unos con otros o corrompan estructuras de datos.
Una ventaja de la programación multihilo es que los programas operan con mayor velocidad en sistemas de computadores con múltiples CPUs (sistemas multiprocesador o a través de grupo de máquinas) ya que los hilos del programa se prestan verdaderamente para la ejecución concurrente. En tal caso el programador necesita ser cuidadoso para evitar condiciones de carrera (problema que sucede cuando diferentes hilos o procesos alteran datos que otros también están usando), y otros comportamientos no intuitivos. Los hilos generalmente requieren reunirse para procesar los datos en el orden correcto. Es posible que los hilos requieran de operaciones atómicas para impedir que los datos comunes sean cambiados o leídos mientras estén siendo modificados, para lo que usualmente se utilizan los semáforos. El descuido de esto puede generar interbloqueo.

Ejemplos Programados (Java):

//Clase ThreadEjemplo que deriva de Thread
//La clase Thread maneja hilos de ejecución en java
public class ThreadEjemplo extends Thread {
public ThreadEjemplo(String str) {
super(str);
}
public void run() {
for (int i = 0; i < 10 ; i++)
System.out.println(i + " " + getName());
System.out.println("Termina thread " + getName());
}
public static void main (String [] args) {
new ThreadEjemplo("Pepe").start();
new ThreadEjemplo("Juan").start();
System.out.println("Termina thread main");
}
}


Ejemplos Programados (C):
//Uso de hilos en C
int lista_ids[10];
void * foo(void * var) // Función que ejecutará el thread
{
   int my_id = *((int*)var);
   printf("Soy el thread %i!\n", my_id);
  
   return NULL;
}

int main(int argc, char *argv[])
{
   int i;
   pthread_t mythread[10];
  
   for(i=0; i~lt~10; i++)
   {
      lista_ids[i] = i+1;
      printf("Creando thread %i\n", i+1);
      pthread_create(&(mythread[i]), NULL, foo, &(lista_ids[i]));
      // Ojo: No vale pasar una variable local, como por ejemplo &i
   }
  
   pthread_exit(NULL);
   return 0;
}

Programación Lógica


Programación Lógica

La programación lógica es un tipo de paradigmas de programación dentro del paradigma de programación declarativa. El resto de los subparadigmas de programación dentro de la programación declarativa son: programación funcional, programación con restricciones, programas DSL (de dominio específico) e híbridos. La programación lógica gira en torno al concepto de predicado, o relación entre elementos. La programación funcional se basa en el concepto de función (que no es más que una evolución de los predicados), de corte más matemático.

Motivación
Históricamente, los ordenadores se han programado utilizando lenguajes muy cercanos a las peculiaridades de la propia máquina: operaciones aritméticas simples, instrucciones de acceso a memoria, etc. Un programa escrito de esta manera puede ocultar totalmente su propósito a la comprensión de un ser humano, incluso uno entrenado. Hoy día, estos lenguajes pertenecientes al paradigma de la Programación imperativa han evolucionado de manera que ya no son tan crípticos.
En cambio, la lógica matemática es la manera más sencilla, para el intelecto humano, de expresar formalmente problemas complejos y de resolverlos mediante la aplicación de reglas, hipótesis y teoremas. De ahí que el concepto de "programación lógica" resulte atractivo en diversos campos donde la programación tradicional es un fracaso.

Aplicaciones
La programación lógica encuentra su hábitat natural en aplicaciones de inteligencia artificial o relacionadas: Sistemas expertos, donde un sistema de información imita las recomendaciones de un experto sobre algún dominio de conocimiento.
  • Demostración automática de teoremas, donde un programa genera nuevos teoremas sobre una teoría existente.
  • Reconocimiento de lenguaje natural, donde un programa es capaz de comprender (con limitaciones) la información contenida en una expresión lingüística humana.
  • Etc.
La programación lógica también se utiliza en aplicaciones más "mundanas" pero de manera muy limitada, ya que la programación tradicional es más adecuada a tareas de propósito general.
La mayoría de los lenguajes de programación lógica se basan en la teoría lógica de primer orden, aunque también incorporan algunos comportamientos de orden superior como la lógica difusa. En este sentido, destacan los lenguajes funcionales, ya que se basan en el cálculo lambda, que es la única teoría lógica de orden superior que es demostradamente computable (hasta el momento).

Información Adicional
La programación funcional es un estilo declarativo de programación basado en:
Exponer una colección de axiomas.
Presentar un teorema objetivo (goal).
La implantación intenta encontrar una colección de axiomas y pasos de inferencia que impliquen el objetivo.
Axiomas expresados como Cláusulas de Horn.
H   B1;B2; : : : ;Bn
El lenguaje combina axiomas existentes utilizando el
proceso de resolución.
C   A;B ^ D   C ) D   A;B
Prolog es el lenguaje más utilizado.

Prolog:
Los programas ejecutan en el contexto de una base de datos de
cláusulas que se asumen ciertas.
Las cláusulas están compuestas por términos: números – enteros o reales.
Átomos: Identificadores que comienzan con minúscula.
Cadena entre comillas.
Secuencia de símbolos de puntuación.
Variables – identificadores que comienzan con mayúscula.
Estructuras – átomo functor y una lista de argumentos

A tiempo de ejecución las variables se instancian con valores específicos como resultados del proceso de unificación.
Alcance limitado a la cláusula en la cual aparece. Verificación dinámica de tipos. Anidamiento arbitrario de estructuras.
Predicado – combinación de un functor y su aridad.
En la base de datos de cláusulas puede haber
Hechos – cláusulas de Horn sin lado derecho.
Reglas – cláusulas de Horn con lado derecho, usando :-
como símbolo de implicación (().
abuelo(X,Y) :- padre(X,Z), padre(Z,Y).
Todas terminan con un punto.
Consulta – cláusula de Horn sin lado izquierdo.
Usualmente escritas en el interpretador.
Ocasionalmente incluidas como “inicialización” o “programa principal” en el texto del programa.


Programación Funcional


Programación Funcional


En ciencias de la computación, la programación funcional es un paradigma de programación declarativa basado en la utilización de funciones aritméticas que no maneja datos mutables o de estado. Enfatiza la aplicación de funciones, en contraste con el estilo de programación imperativa, que enfatiza los cambios de estado. La programación funcional tiene sus raíces en el cálculo lambda, un sistema formal desarrollado en los 1930s para investigar la definición de función, la aplicación de las funciones y la recursión. Muchos lenguajes de programación funcionales pueden ser vistos como elaboraciones del cálculo lambda.
En la práctica, la diferencia entre una función matemática y la noción de una "función" utilizada en la programación imperativa es que las funciones imperativas pueden tener efectos secundarios, al cambiar el valor de cálculos realizados previamente. Por esta razón carecen de transparencia referencial, es decir, la misma expresión lingüística puede resultar en valores diferentes en diferentes momentos dependiendo del estado del programa siendo ejecutado. Con código funcional, en contraste, el valor generado por una función depende exclusivamente de los argumentos alimentados a la función. Al eliminar los efectos secundarios se puede entender y predecir el comportamiento de un programa mucho más fácilmente, y esta es una de las principales motivaciones para utilizar la programación funcional.
Los lenguajes de programación funcional, especialmente los que son puramente funcionales, han sido enfatizados en el ambiente académico principalmente y no tanto en el desarrollo de software comercial. Sin embargo, lenguajes de programación importantes tales como Scheme, Erlang, Objective Caml y Haskel, han sido utilizados en aplicaciones comerciales e industriales por muchas organizaciones. La programación funcional también es utilizada en la industria a través de lenguajes de dominio específico como R (estadística), Mathematica (matemáticas simbólicas), J y K (análisis financiero), F# en Microsoft.NET y XSLT (XML). Lenguajes de uso específico usados comúnmente como SQL y Lex/Yacc, utilizan algunos elementos de programación funcional, especialmente al procesar valores mutables. Las hojas de cálculo también pueden ser consideradas lenguajes de programación funcional.
La programación funcional también puede ser desarrollada en lenguajes que no están diseñados específicamente para la programación funcional. En el caso de Perl, por ejemplo, que es un lenguaje de programación imperativo, existe un libro que describe como aplicar conceptos de programación funcional. JavaScript, uno de los lenguajes más ampliamente utilizados en la actualidad, también incorpora capacidades de programación funcional.


Características
Los programas escritos en un lenguaje funcional están constituidos únicamente por definiciones de funciones, entendiendo éstas no como subprogramas clásicos de un lenguaje imperativo, sino como funciones puramente matemáticas, en las que se verifican ciertas propiedades como la transparencia referencial (el significado de una expresión depende únicamente del significado de sus subexpresiones), y por tanto, la carencia total de efectos colaterales.
Otras características propias de estos lenguajes son la no existencia de asignaciones de variables y la falta de construcciones estructuradas como la secuencia o la iteración (lo que obliga en la práctica a que todas las repeticiones de instrucciones se lleven a cabo por medio de funciones recursivas).
Existen dos grandes categorías de lenguajes funcionales: los funcionales puros y los híbridos. La diferencia entre ambos estriba en que los lenguajes funcionales híbridos son menos dogmáticos que los puros, al admitir conceptos tomados de los lenguajes imperativos, como las secuencias de instrucciones o la asignación de variables. En contraste, los lenguajes funcionales puros tienen una mayor potencia expresiva, conservando a la vez su transparencia referencial, algo que no se cumple siempre con un lenguaje funcional híbrido.
Notas adicionales
Todo se representa en el paradigma funcional como expresiones, números son expresiones y operandos son expresiones, etc.
El conjunto de operadores y operandos representan una combinación.
El paréntesis representa la aplicación funcional, esto hace que la agrupación básica por paréntesis deba tratarse de forma cuidadosa, para evitar tener errores sintácticos.
Un programa en el paradigma funcional se basa en:
  1. definir funciones
  2. componer funciones
  3. evaluar funciones
Las ejecuciones no son como en lenguajes procedimentales, se basan en un concepto particular.
El intérprete sigue los siguientes pasos:
  1. Evaluar las subexpresiones (expresiones que están a la derecha de la función)
  2. Aplicar la función que es el valor que se encuentra más a la izquierda en la combinación (operador) a los argumentos que se encuentran a su derecha (operandos)
Ejemplo:
(+ 1 2 3 4 5) -----> 15
(ValorAbsoluto -100) --------> 100
La notación es prefija por defecto para la construcción de combinaciones.
El orden de evaluación es aplicativo, es decir se evalúan argumentos antes que las funciones.
La Programación Funcional define la salida de un programa como una función de sus entradas, excluyendo la noción de estado y efecto de borde.
Funciones que retornan valores estructurados: en lenguajes funcionales es posible retornar funciones con valores no primitivos , es decir, arreglos, listas, etc.
Polimorfismo dinámico (Scheme) o estático (Haskell): En el caso de scheme usa polimorfismo dinámico o paramétrico que quiere decir que un procedimiento puede tener parámetros con tipos no especificados, y al usarse con un tipo determinado se crean las asociaciones correctas. Esto gracias a que Scheme usa manejo dinámico de tipos. En el caso de haskell usa polimorfismo estático como en lenguaje c, donde se debe especificar el tipo de cada parámetro.
Tipo Lista con abundantes operadores.
Recursión: La recursión se da de forma natural en lenguajes funcionales.
Tipos agregados definibles por el usuario: se hacen mediante la aplicación del “define”, otorgándole nombre a las combinaciones que definen el tipo
Recolección de basura: las asociaciones se destruyen automáticamente luego que su tiempo de vida expira.
La asociación es estática para nombres.
Constructores de asociación: definen bloques que contienen asociaciones dentro. Son let, let* y letrec.
Lista de asociaciones paralelas (let): los valores iníciales son computados antes de que cualquier variable sea atada a una asociación.
Lista de asociaciones ordenadas (let*): las asociaciones y evaluaciones son realizadas en orden secuencial.
Lista de asociaciones recursivas (letrec): todas las asociaciones están en efecto mientras sus valores iníciales son computados.


Listas heterogéneas.
Funciones para descomposición (car, cdr y sus
extensiones).
Funciones para composición (cons, append y list)
Predicados (null? para vacuidad).
Listas de Asociación (assq para búsqueda).
Predicados para comparación: se refiere a que cada comparación hecha por ejemplo por condicionales se trata como un predicado, que puede tener asociado un valor verdadero o falso.
Booleanos (#t y #f): verdadero o falso respectivamente.
Comparación:
¿Son el mismo objeto? (eq?).
¿Son semánticamente equivalentes? (eqv?).
¿Son estructuralmente equivalentes? (equal?).
Estas son estructuras que puedes usar que retornan el valor de los predicados, es decir verdadero o falso.
Selección Simple (if).
Selección Generalizada (cond).
Las estructuras de control en la programación funcional son las condiciones y la recursión.
Se pueden definir funciones de orden superior, que son funciones que reciben funciones como parámetros o devuelven funciones. Emplean de igual modo funciones de primer orden.

 Ejemplos Programados (En Mit-Scheme)


;Iteracion simulada:
(define x 0)
(do ((i 1 (+ i 1))) ; i = 1, i = i+1
            ((> i 10) i) ;condicion de parada que se interpreta como: cuando i sea mayor que 10
            (set! x (* i 10)) ;procedimientos normales
            (display " ") ;procedimientos normales
            (display x) ;procedimientos normales
)

;condiciones multiples:
(cond ((> 3 2) 'greater)
      ((< 3 2) 'less)
               ()
               ()
)

;Funciones de orden superior
(define funcion (lambda(a b) (a b)))
;es definir una función pero pasándole como argumento una función. Recordar que la definición dice
;que funciones de orden superior reciben parámetros que son funciones o retornan funciones.
;para definirla se defina una función lambda y se le da una asociación, de definen los parámetros de forma normal
;pero en el cuerpo se ve reflejada la aplicación funcional (recordar que cuando se evalúa una expresión al elemento mas
;a la izquierda se le toma como función y a los que están a su derecha parámetros, por ende hacer (a b) es equivalente a
;tomar la función a y aplicarla al parámetro b