Simultaneidad vs paralelismo

Tanto la concurrencia como el paralelismo se utilizan en relación con los programas multiproceso, pero existe mucha confusión sobre la similitud y la diferencia entre ellos. La gran pregunta a este respecto: ¿es el paralelismo de concurrencia o no? Aunque ambos términos parecen bastante similares, pero la respuesta a la pregunta anterior es NO, la concurrencia y el paralelismo no son lo mismo. Ahora bien, si no son iguales, ¿cuál es la diferencia básica entre ellos?

En términos simples, la concurrencia se ocupa de administrar el acceso al estado compartido desde diferentes subprocesos y, por otro lado, el paralelismo se ocupa de utilizar múltiples CPU o sus núcleos para mejorar el rendimiento del hardware.

Concurrencia en detalle

La concurrencia es cuando dos tareas se superponen en la ejecución. Podría ser una situación en la que una aplicación avanza en más de una tarea al mismo tiempo. Podemos entenderlo esquemáticamente; varias tareas están progresando al mismo tiempo, de la siguiente manera:

Niveles de simultaneidad

En esta sección, discutiremos los tres niveles importantes de concurrencia en términos de programación:

Simultaneidad de bajo nivel

En este nivel de concurrencia, existe un uso explícito de operaciones atómicas. No podemos utilizar este tipo de simultaneidad para la creación de aplicaciones, ya que es muy propenso a errores y difícil de depurar. Incluso Python no admite este tipo de simultaneidad.

Simultaneidad de nivel medio

En esta concurrencia, no se utilizan operaciones atómicas explícitas. Utiliza los bloqueos explícitos. Python y otros lenguajes de programación admiten este tipo de simultaneidad. La mayoría de los programadores de aplicaciones utilizan esta simultaneidad.

Simultaneidad de alto nivel

En esta concurrencia, no se utilizan operaciones atómicas explícitas ni bloqueos explícitos. Python tieneconcurrent.futures módulo para soportar este tipo de simultaneidad.

Propiedades de los sistemas concurrentes

Para que un programa o sistema concurrente sea correcto, debe satisfacer algunas propiedades. Las propiedades relacionadas con la terminación del sistema son las siguientes:

Propiedad de corrección

La propiedad de corrección significa que el programa o el sistema debe proporcionar la respuesta correcta deseada. Para simplificarlo, podemos decir que el sistema debe asignar correctamente el estado del programa inicial al estado final.

Propiedad de seguridad

La propiedad de seguridad significa que el programa o el sistema debe permanecer en “good” o “safe” estado y nunca hace nada “bad”.

Propiedad de vivacidad

Esta propiedad significa que un programa o sistema debe “make progress” y alcanzaría algún estado deseable.

Actores de sistemas concurrentes

Esta es una propiedad común del sistema concurrente en el que puede haber múltiples procesos e hilos, que se ejecutan al mismo tiempo para progresar en sus propias tareas. Estos procesos e hilos se denominan actores del sistema concurrente.

Recursos de sistemas concurrentes

Los actores deben utilizar los recursos como la memoria, el disco, la impresora, etc. para realizar sus tareas.

Cierto conjunto de reglas

Cada sistema concurrente debe poseer un conjunto de reglas para definir el tipo de tareas que deben realizar los actores y el tiempo para cada una. Las tareas pueden ser adquirir bloqueos, compartir memoria, modificar el estado, etc.

Barreras de sistemas concurrentes

Al implementar sistemas concurrentes, el programador debe tener en cuenta los siguientes dos problemas importantes, que pueden ser las barreras de los sistemas concurrentes:

Intercambio de datos

Un tema importante al implementar los sistemas concurrentes es el intercambio de datos entre múltiples subprocesos o procesos. En realidad, el programador debe asegurarse de que los bloqueos protejan los datos compartidos para que todos los accesos a ellos sean serializados y solo un hilo o proceso pueda acceder a los datos compartidos a la vez. En caso de que, cuando varios subprocesos o procesos intenten acceder a los mismos datos compartidos, no todos, sino al menos uno de ellos, se bloquearían y permanecerían inactivos. En otras palabras, podemos decir que podríamos usar solo un proceso o subproceso a la vez cuando el bloqueo esté en vigor. Puede haber algunas soluciones simples para eliminar las barreras mencionadas anteriormente:

Restricción de uso compartido de datos

La solución más simple es no compartir ningún dato mutable. En este caso, no necesitamos usar bloqueo explícito y se resolvería la barrera de concurrencia debido a datos mutuos.

Asistencia de estructura de datos

Muchas veces los procesos concurrentes necesitan acceder a los mismos datos al mismo tiempo. Otra solución, además del uso de bloqueos explícitos, es usar una estructura de datos que admita el acceso concurrente. Por ejemplo, podemos usar elqueuemódulo, que proporciona colas seguras para subprocesos. También podemos usarmultiprocessing.JoinableQueue clases para la concurrencia basada en multiprocesamiento.

Transferencia de datos inmutable

A veces, la estructura de datos que estamos usando, digamos la cola de concurrencia, no es adecuada, entonces podemos pasar los datos inmutables sin bloquearlos.

Transferencia de datos mutable

Como continuación de la solución anterior, suponga que si se requiere pasar solo datos mutables, en lugar de datos inmutables, entonces podemos pasar datos mutables que son de solo lectura.

Compartir recursos de E / S

Otro tema importante en la implementación de sistemas concurrentes es el uso de recursos de E / S por subprocesos o procesos. El problema surge cuando un subproceso o proceso está usando la E / S durante tanto tiempo y el otro está inactivo. Podemos ver este tipo de barrera mientras trabajamos con una aplicación pesada de E / S. Se puede entender con la ayuda de un ejemplo, la solicitud de páginas desde el navegador web. Es una aplicación pesada. Aquí, si la velocidad a la que se solicitan los datos es más lenta que la velocidad a la que se consumen, entonces tenemos una barrera de E / S en nuestro sistema concurrente.

El siguiente script de Python es para solicitar una página web y obtener el tiempo que nuestra red tardó en obtener la página solicitada:

import urllib.request

import time

ts = time.time()

req = urllib.request.urlopen('http://www.tutorialspoint.com')

pageHtml = req.read()

te = time.time()

print("Page Fetching Time : {} Seconds".format (te-ts))

Después de ejecutar el script anterior, podemos obtener el tiempo de recuperación de la página como se muestra a continuación.

Salida

Page Fetching Time: 1.0991398811340332 Seconds

Podemos ver que el tiempo para recuperar la página es más de un segundo. Ahora, ¿qué pasa si queremos buscar miles de páginas web diferentes? Puede comprender cuánto tiempo tomaría nuestra red.

¿Qué es el paralelismo?

El paralelismo puede definirse como el arte de dividir las tareas en subtareas que se pueden procesar simultáneamente. Es lo opuesto a la concurrencia, como se discutió anteriormente, en la que dos o más eventos están sucediendo al mismo tiempo. Podemos entenderlo esquemáticamente; una tarea se divide en varias subtareas que se pueden procesar en paralelo, de la siguiente manera:

Para tener más idea sobre la distinción entre concurrencia y paralelismo, considere los siguientes puntos:

Concurrente pero no paralelo

Una aplicación puede ser concurrente pero no paralela significa que procesa más de una tarea al mismo tiempo, pero las tareas no se dividen en subtareas.

Paralelo pero no concurrente

Una aplicación puede ser paralela pero no concurrente significa que solo funciona en una tarea a la vez y las tareas divididas en subtareas se pueden procesar en paralelo.

Ni paralelo ni concurrente

Una aplicación no puede ser paralela ni concurrente. Esto significa que solo funciona en una tarea a la vez y la tarea nunca se divide en subtareas.

Tanto en paralelo como concurrente

Una aplicación puede ser tanto paralela como concurrente, lo que significa que ambas funcionan en varias tareas a la vez y la tarea se divide en subtareas para ejecutarlas en paralelo.

Necesidad de paralelismo

Podemos lograr el paralelismo distribuyendo las subtareas entre diferentes núcleos de una sola CPU o entre múltiples computadoras conectadas dentro de una red.

Considere los siguientes puntos importantes para comprender por qué es necesario lograr el paralelismo:

Ejecución de código eficiente

Con la ayuda del paralelismo, podemos ejecutar nuestro código de manera eficiente. Nos ahorrará tiempo porque el mismo código en partes se ejecuta en paralelo.

Más rápido que la computación secuencial

La computación secuencial está limitada por factores físicos y prácticos debido a los cuales no es posible obtener resultados de computación más rápidos. Por otro lado, este problema se resuelve mediante la computación paralela y nos brinda resultados de computación más rápidos que la computación secuencial.

Menos tiempo de ejecución

El procesamiento en paralelo reduce el tiempo de ejecución del código del programa.

Si hablamos del ejemplo de la vida real de paralelismo, la tarjeta gráfica de nuestra computadora es el ejemplo que resalta el verdadero poder del procesamiento paralelo porque tiene cientos de núcleos de procesamiento individuales que funcionan de forma independiente y pueden hacer la ejecución al mismo tiempo. Por esta razón, también podemos ejecutar aplicaciones y juegos de alta gama.

Comprensión de los procesadores para la implementación.

Conocemos la concurrencia, el paralelismo y la diferencia entre ellos, pero ¿qué pasa con el sistema en el que se va a implementar? Es muy necesario tener el conocimiento del sistema, sobre el que vamos a implementar, porque nos da el beneficio de tomar decisiones informadas al diseñar el software. Tenemos los siguientes dos tipos de procesadores:

Procesadores de un solo núcleo

Los procesadores de un solo núcleo son capaces de ejecutar un hilo en cualquier momento. Estos procesadores utilizancontext switchingpara almacenar toda la información necesaria para un hilo en un momento específico y luego restaurar la información más tarde. El mecanismo de cambio de contexto nos ayuda a progresar en una serie de subprocesos en un segundo determinado y parece que el sistema está trabajando en varias cosas.

Los procesadores de un solo núcleo tienen muchas ventajas. Estos procesadores requieren menos energía y no existe un protocolo de comunicación complejo entre varios núcleos. Por otro lado, la velocidad de los procesadores de un solo núcleo es limitada y no es adecuada para aplicaciones más grandes.

Procesadores multinúcleo

Los procesadores multinúcleo tienen varias unidades de procesamiento independientes también llamadas cores.

Estos procesadores no necesitan un mecanismo de cambio de contexto ya que cada núcleo contiene todo lo que necesita para ejecutar una secuencia de instrucciones almacenadas.

Ciclo de búsqueda-decodificación-ejecución

Los núcleos de los procesadores de varios núcleos siguen un ciclo de ejecución. Este ciclo se llamaFetch-Decode-Executeciclo. Implica los siguientes pasos:

Ir a buscar

Este es el primer paso del ciclo, que implica la obtención de instrucciones de la memoria del programa.

Descodificar

Las instrucciones obtenidas recientemente se convertirán en una serie de señales que activarán otras partes de la CPU.

Ejecutar

Es el paso final en el que se ejecutarían las instrucciones obtenidas y decodificadas. El resultado de la ejecución se almacenará en un registro de la CPU.

Una ventaja aquí es que la ejecución en procesadores de múltiples núcleos es más rápida que la de los procesadores de un solo núcleo. Es adecuado para aplicaciones más grandes. Por otro lado, el protocolo de comunicación complejo entre múltiples núcleos es un problema. Varios núcleos requieren más energía que los procesadores de un solo núcleo.