2

Programación concurrente y paralelismo [Modulo 1 - Threads] - [2. Threads y Procesos]

Para poder tener una mejor comprensión sobre la concurrencia, es necesario entender y tener dominio sobre 2 conceptos claves: Threads y Procesos. Puede llegar a ser un poco complejo pero es importante conocer sus diferencias y claro, sus similitudes.

Nota: este articulo tendrá toda la teoría necesaria al inicio y se finalizara con la implementación de threads al final.

Procesos

En palabras breves, un proceso no es mas que la instancia de un programa . Cuando se abre un programa, ya sea un navegador, calculadora, reproductor, etc. De manera interna se esta creando un nuevo proceso.

  • Un proceso es la ejecución del programa mismo.

Proceso != Programa

Es importante aclarar que un proceso es la tarea de ejecutar un programa en su totalidad. Mientras que un programa es una serie de instrucciones y datos que se usan para un fin especifico.

Así es como un programa puede ser ejecutado múltiples veces y cada uno será ejecutado desde un proceso diferente.

Dentro de un proceso encontraremos todo lo necesario para que el programa viva y se ejecute de manera correcta. Es decir, dentro del proceso encontraremos el código fuente, variables, ficheros, sub-procesos, etc.

Sistema Operativo

Esta es una entidad importante para comprender de mejor manera el tema de procesos. El es el encargado de crear, ejecutar y controlar los procesos,

Sin título.png

Una de las tareas importantes del sistema operativo es que se encarga de aislar los procesos entre ellos, para así hacer a cada uno independiente. Estos con el fin de que no compartan información entre procesos y puedan generar errores. Es por esto que un programa no puede acceder a la información de otro programa en ejecución. Cuando un proceso finaliza, sea naturalmente o no, debido a un error, el sistema operativo es quien se encarga de liberar el espacio en memoria.

Con los procesos es posible implementar el paralelismo, ejecutando diferentes procesos en diferentes procesadores.

Threads

Los threads también son conocidos comosub-procesos o hilos y se pueden definir como una secuencia de instrucciones las cuales el sistema operativo puede programar para su ejecución. Su diferencia con los procesos es que son entidades mucho mas pequeñas y esto los hace aun mas fácil de procesar.

  • Un thread es la unidad mas pequeña a la cual un procesador puede asignar tiempo.
  • Un thread vive dentro de un proceso y un proceso vive dentro del sistema operativo.

A diferencia de los procesos es que, como los threads viven dentro de un proceso, estos pueden llegar a compartir información entre ellos. Esto puede darnos muchas ventajas y se le puede sacar mucho provecho, pero puede llegar a ser complejo de manejar, ya que podemos encontrarnos con problemas como el Race Condition.

Race Condition

En esencia, es un problema que surge cuando mas de un thread intenta acceder y modificar un espacio de memoria compartido, coaccionando así que el programa se comporte de manera inadecuada.

Codigo

Para este ejemplo vamos a consumir la API de Pokemon para obtener el nombre de un Pokemon aleatorio (entre los primeros 151 Pokemones 😁), realizaremos un ejemplo con programación secuencial y luego lo transformaremos a programación concurrente usando threads.

Forma secuencial

import requests
from random import randint

def get_pokemon_name():
	response = requests.get(f"https://pokeapi.co/api/v2/pokemon/{randint(1, 151)}")
	if response.status_code == 200:
		result = response.json()
		name = result.get("forms")[0].get("name")
		print(name)

if __name__ == "__main__":
	# Secuencial
	get_pokemon_name()

Como podemos ver, el programa principal es muy sencillo y fácil de entender. Pero ahora vamos a ver la diferencia de hacerlo con concurrencia.

Forma concurrente

Lo primero a tener en cuenta es que debemos importar el modulo threading para poder usar los threads, luego generaremos una instancia de la clase Thread, la cual va a recibir un target como parámetro, este target es la función que queremos que ejecute en esta instancia del Thread que estamos crenado. En este caso, pasaremos el nombre de la función get_pokemon_name. Finalizaremos ejecutando el metodo start del objeto que acabamos de crear para levantar nuestro thread y que este inicie su ejecución.

import requests
from random import randint
import threading

def get_pokemon_name():
	response = requests.get(f"https://pokeapi.co/api/v2/pokemon/{randint(1, 151)}")
	if response.status_code == 200:
		result = response.json()
		name = result.get("forms")[0].get("name")
		print(name)

if __name__ == "__main__":
	# Concurrente
	thread = threading.Thread(target=get_pokemon_name)
	thread.start()

¡Felicitaciones! El thread habrá funcionado de forma correcta y su ejecución habrá sido en un thread diferente al thread principal.
Si quisiéramos levantar varios hilos al mismo tiempo, podemos agregar un ciclo, de esta forma.

if __name__ == "__main__":
	# Concurrente
	for _ in range(5):
		thread = threading.Thread(target=get_pokemon_name)
		thread.start()

Y si volvemos a ejecutar, tendremos 5 Pokemones al azar y cada uno habrá sido consultado desde un hilo diferente.

Escribe tu comentario
+ 2
1

Muchas gracias por el aporte, excelente explicación en los conceptos de procesos e hilos 🙌💪. Sólo quciera agregar lo siguiente: el interprete oficial de Python (CPython) tiene algo que se llama GIL (Global Interpreter Lock), que al final del día lo que hace es ejecutar todo dentro un único procesador y cuando usamos la clase Thread emula un paralelismo, al estar saltando entre hilos constantemente; esto a diferencia de otros lenguajes donde los threads si son ejecutados al mismo tiempo por procesadores diferentes.

1
23651Puntos
9 meses

Así es compañero, tienes toda la razón y muchas gracias por tu aporte. Seguiré publicado mas artículos sobre la concurrencia y paralelismo, entre los planes estaba hablar sobre GIL casi finalizando la sección de hilos. Sin embargo también es muy apropiado mencionarlo aquí. 🔥