Ejemplo para introducción al paralelismo

Introduccion

Algunos alumnos dificilmente visualizan como se puede convertir un programa "en serie" en "paralelo". A continuación se presenta el caso de un programa hecho usando el paradigma de programacion orientada a objetos ( por sus siglas POO ) en C++ que es convertido a la programación paralela siguiendo el mismo paradigma de programación.

 

El programa en POO "serial"

En las primeras clases de lógica de programación en la Universidad Autónoma de Guadalajara [1] ( por sus siglas UAG ) se utiliza el siguiente ejercicio:
 
Realiza un programa para ejecutarse sobre  Ms Windows usando el "símbolo del sistema" - modo real o virtual del Ms Windows[2] - que muestre un asterisco rebotando en forma diagonal por la pantalla (al chocar con sus bordes ). El programa termina cuando se presiona cualquier tecla.

**********************
asterisco.h
**********************
#include <windows.h>

class Asterisco
{
	public:
		Asterisco();
		int getCol();
		int getRen();
		void setCol( int valor );
		void setRen( int valor );
		void mostrar();
		int getSentido();
		void setSentido( int valor );
		void mover();

	private:
		int col;
		int ren;
		char simbolo;
		int sentido;
		void borrar();
};

**********************
asterisco.cpp
**********************
#include <stdio.h>
#include <conio.h>
#include <windows.h>
#include "asterisco.h"

Asterisco::Asterisco()
{
	simbolo='*';
	sentido=0;
}

int Asterisco::getCol()
{
	return col;
}

int Asterisco::getSentido()
{
	return sentido;
}

int Asterisco::getRen()
{
	return ren;
}

void Asterisco::setCol( int valor )
{
	col= valor;
}

void Asterisco::setSentido( int valor )
{
	sentido=valor;
}

void Asterisco::setRen( int valor )
{
	ren= valor;
}

void Asterisco::mostrar()
{
	gotoxy( getCol(), getRen() );
	printf("%c", simbolo, ren );
}

void Asterisco::borrar()
{
	gotoxy( getCol(), getRen() );
	printf(" " );
}


void Asterisco::mover()
{
	borrar();
	switch ( sentido )
	{
		case 0:	//superior derecha
			if ( ren>1)
			{
				if (col < 79 )
				{
					ren-=1;
					col+=1;
				}
				else
					sentido= 1;
			}
			else
				sentido=2;
			break;
		case 1:	//superior izquierda
			if ( ren>1 )
			{
				if ( col>1 )
				{
					ren-=1;
					col-=1;
				}
				else
					sentido= 0;
			}
			else
				sentido= 3;
			break;
		case 2:	//inferior derecha
			if ( ren<24 )
			{
				if ( col< 80 )
				{
					ren+=1;
					col+=1;
				}
				else
					sentido= 3;
			}
			else
				sentido= 0;
			break;
		case 3:	//inferior izquierda
			if ( ren< 24 )
			{
				if ( col > 1 )
				{
					ren+=1;
					col-=1;
				}
				else
					sentido= 2;
			}
			else
				sentido= 1;
			break;
	}
	mostrar();
}

**********************
inicio.cpp
**********************
#include <conio.h >
#include <windows.h >
#include "asterisco.h"

void main()
{
	Asterisco *obj= new Asterisco();
	clrscr();
	obj->setCol( 20 );
	obj->setRen( 18 );
	obj->setSentido( 0 );
	obj->mostrar();
	do
	{
		Sleep( 200 );
		obj->mover();
	} while ( !kbhit() );
}
 
El programa usará el compilador gratuito de Borland en su version 5.5[3] de la siguiente forma:
 
bcc32 asterisco.cpp inicio.cpp
 

El programa POO paralelo


Posteriormente se solicita a los alumnos que modifiquen el anterior programa para generar varios asteriscos y que cada uno vaya rebotando en la pantalla en direcciones distintas ( el programa coloca posiciones iniciales aleatorias con un sentido inicial aleatorio ). Una posible solución es usar un arreglo de objetos de la clase asterisco. Por medio de un ciclo recorrer constantemente todos los objetos y mover un asterisco a la vez para dar la impresión al usuario que se mueven todos los asteriscos al mismo tiempo. El código es el siguiente:

********************
asterisco.h
********************
 
#include < windows.h >

class Asterisco
{
	public:
		Asterisco();
		int getCol();
		int getRen();
		void setCol( int valor );
		void setRen( int valor );
		void mostrar();
		int getSentido();
		void setSentido( int valor );
		int getTermino();
		void setTermino( int valor );
		void mover();

	private:
		int col;
		int ren;
		char simbolo;
		int sentido;
		char termino;
		
		void borrar();
};
********************
asterisco.cpp
********************
 
#include < stdio.h >
#include < conio.h >
#include < windows.h >
#include "asterisco.h"

Asterisco::Asterisco()
{
	simbolo='*';
	sentido=0;
	termino='a';
}

int Asterisco::getCol()
{
	return col;
}

int Asterisco::getSentido()
{
	return sentido;
}

int Asterisco::getRen()
{
	return ren;
}

void Asterisco::setCol( int valor )
{
	col= valor;
}

void Asterisco::setSentido( int valor )
{
	sentido=valor;
}

void Asterisco::setRen( int valor )
{
	ren= valor;
}

void Asterisco::mostrar()
{
	gotoxy( getCol(), getRen() );
	printf("%c", simbolo, ren );
}

void Asterisco::borrar()
{
	gotoxy( getCol(), getRen() );
	printf(" " );
}

void Asterisco::mover()
{
	borrar();
	switch ( sentido )
	{
		case 0:	//superior derecha
			if ( ren>1)
			{
				if (col < 79 )
				{
					ren-=1;
					col+=1;
				}
				else
					sentido= 1;
			}
			else
				sentido=2;
			break;
		case 1:	//superior izquierda
			if ( ren>1 )
			{
				if ( col>1 )
				{
					ren-=1;
					col-=1;
				}
				else
					sentido= 0;
			}
			else
				sentido= 3;
			break;
		case 2:	//inferior derecha
			if ( ren<24 )
			{
				if ( col< 80 )
				{
					ren+=1;
					col+=1;
				}
				else
					sentido= 3;
			}
			else
				sentido= 0;
			break;
		case 3:	//inferior izquierda
			if ( ren< 24 )
			{
				if ( col > 1 )
				{
					ren+=1;
					col-=1;
				}
				else
					sentido= 2;
			}
			else
				sentido= 1;
			break;
	}
	mostrar();
}

int Asterisco::getTermino()
{
	return termino;
}

void Asterisco::setTermino( int valor )
{
	termino= valor;
}

 
********************
inicio.cpp
********************
#include <conio.h >
#include <windows.h >
#include <stdio.h >
#include <stdlib.h >
#include "asterisco.h"


const int CANTIDAD_ASTERISCOS= 3;

void main()
{
	Asterisco *obj[ CANTIDAD_ASTERISCOS ];
	int i;
	clrscr();
	randomize();
	for(i=0; i<CANTIDAD_ASTERISCOS ; i+=1 )
	{
		obj[i]= new Asterisco();
		obj[i]->setCol( random( 80 ) );
		obj[i]->setRen( random( 24 ) );
		obj[i]->setSentido( random( 4 ) );
		obj[i]->mostrar();
	}
	i=0;
	do
	{
		Sleep( 250);
		obj[i++]->mover();
		i= ( i>= CANTIDAD_ASTERISCOS ? 0 : i );
	} while ( !kbhit() );
}
 
El único cambio ocurrio en el código que contenia al main. En este momento es donde más fácilmente se puede introducir la programación paralela y la ventaja de los hilos; al controlar cada uno un asterisco. El código que se genera solo se modifica en pocos lugares.
 
********************
asterisco.h
********************
 
#include <windows.h>

class Asterisco
{
	public:
		Asterisco();
		int getCol();
		int getRen();
		void setCol( int valor );
		void setRen( int valor );
		void mostrar();
		int getSentido();
		void setSentido( int valor );
		void iniciar();                  //nuevo método
		int getTermino();
		void setTermino( int valor );

	private:
		int col;
		int ren;
		char simbolo;
		int sentido;
		char termino;
		
		void borrar();
		void mover();                    //ahora es privado
};
 
********************
asterisco.cpp
********************
 
#include <stdio.h >
#include <conio.h >
#include <windows.h >
#include "asterisco.h"

Asterisco::Asterisco()
{
	simbolo='*';
	sentido=0;
	termino='p';
}

int Asterisco::getCol()
{
	return col;
}

int Asterisco::getSentido()
{
	return sentido;
}

int Asterisco::getRen()
{
	return ren;
}

void Asterisco::setCol( int valor )
{
	col= valor;
}

void Asterisco::setSentido( int valor )
{
	sentido=valor;
}

void Asterisco::setRen( int valor )
{
	ren= valor;
}

void Asterisco::mostrar()
{
	gotoxy( getCol(), getRen() );
	printf("%c", simbolo );
}

void Asterisco::borrar()
{
	gotoxy( getCol(), getRen() );
	printf(" " );
}

void Asterisco::iniciar()  //contiene el ciclo que antes estaba en el main
{
	termino='a';
	do
	{
		Sleep( 200 );
		mover();
	} while ( !kbhit() );
	termino='f';
}

void Asterisco::mover()
{
	borrar();
	switch ( sentido )
	{
		case 0:	//superior derecha
				if ( ren > 1)
				{
					if (col < 79 )
					{
						ren-=1;
						col+=1;
					}
					else
						sentido= 1;
				}
				else
					sentido=2;
				break;
		case 1:	//superior izquierda
				if ( ren > 1 )
				{
					if ( col > 1 )
					{
						ren-=1;
						col-=1;
					}
					else
						sentido= 0;
				}
				else
					sentido= 3;
				break;
		case 2:	//inferior derecha
				if ( ren < 24 )
				{
					if ( col < 80 )
					{
						ren+=1;
						col+=1;
					}
					else
						sentido= 3;
				}
				else
					sentido= 0;
				break;
		case 3:	//inferior izquierda
				if ( ren < 24 )
				{
					if ( col > 1 )
					{
						ren+=1;
						col-=1;
					}
					else
						sentido= 2;
				}
				else
					sentido= 1;
				break;
	}
	mostrar();
}

int Asterisco::getTermino()
{
	return termino;
}

void Asterisco::setTermino( int valor )
{
	termino= valor;
}

 
********************
inicio.cpp
********************
 
#include <conio.h>
#include <windows.h>
#include <stdio.h>
#include "asterisco.h"
#include <stdlib.h>

DWORD WINAPI iniciarAsterisco(LPVOID param);

const int CANTIDAD_HILOS = 3;

Asterisco *obj[ CANTIDAD_HILOS ];

void main()
{
	HANDLE hilos[ CANTIDAD_HILOS ];
	int i;
	int *valorEntero;
	void *parametroVoid= NULL;
	randomize();
	clrscr();
	for( i=0; i < CANTIDAD_HILOS ;i += 1 )
	{
		obj[ i ] = new Asterisco();
		obj[ i ] -> setCol( random(80) );
		obj[ i ] -> setRen( random(24) );
		obj[ i ] -> setSentido( random(5) );
		obj[ i ] -> mostrar();
	}
	parametroVoid= malloc( sizeof(int ) );
	valorEntero= (int *) parametroVoid;
	for( i=0; i< CANTIDAD_HILOS ;i += 1 )
	{
		*valorEntero= i;
		if ( ( hilos[i]= CreateThread( NULL, 0, iniciarAsterisco, parametroVoid, 0, NULL )) == NULL )
			printf("error en hilo 0");
		Sleep( 500 );
	}
	WaitForMultipleObjects( CANTIDAD_HILOS, hilos, true, INFINITE );
}

DWORD WINAPI iniciarAsterisco(LPVOID param)
{
	int *i= (int *) param;
	obj[ (*i) ]->iniciar();
	return 0;
}

Nota: En algunas computadoras se generan más asteriscos de los esperados por errores en el manejo de la memoria.
 
La instrucción CreateThread se utiliza en un ciclo para generar 3 hilos, uno para cada asterisco. Hay muchas otras diapositivas y artículos donde se habla de esta instrucción por eso se no consideró oportuno hablar de ella.
 
Trabajos futuros
  • Este código evidencía el uso del tipo de dato  "void" . Posteriormente en otros ejemplos se profundizará su estudio.
  • En esta ocasión se usó un arreglo de hilos que manejaban objetos de la misma clase, pero cuando estos sean diferentes ¿qué cambios habrà?
  • Realizar este juego usando  una interface gráfica de windows.
  • En el ejemplo se utiliza la función Sleep para detener el flujo principal ( el main del programa ) entre cada generación de hilo, esto es por la variable compartida que se pasa como parámetro para inicializar cada hilo. De no hacerlo ¿qué sucede? Este es un ejercicio que hará reflexionar a los estudiantes sobre la forma de trabajar de los hilos y que es tratada en varios de los cursos en línea.
Referencias
[1] Universidad Autónoma de Guadalajara. http://www.uag.mx
[2] Real Mode of Ms Windows.  Wikipedia. http://en.wikipedia.org/wiki/Real_mode
[3] Borland C++ Compiler version 5.5 Free Download. Embarcadero developer network. http://edn.embarcadero.com/article/20633
 
Para obtener información más completa sobre las optimizaciones del compilador, consulte nuestro Aviso de optimización.