Diciembre 27, 2007
Diciembre 25, 2007
Tutorial: Como ver fotos y videos desde consola
Tomado de:
http://fitoria.blogspot.com/2007/11/tutorial-como-ver-videos-y-fotos-desde.html#commentsBueno aca algo bien interesante y chistoso. Caca-utils(feo el nombre en nuestro idioma) es un grupito de programas que hacen posible ver fotos y videos desde una consola o modo texto. Algo asi como arte ASCII(ASCII art). pero con tus fotos y videos.
Primero tenemos que instalar CACA-UTILS en nuestro sistema GNU/Linux.
Despues podremos usar algunas de sus herramientas como cacaview que convierte tus imagenes( *.png, *.jpge, entre otros) en Arte ASCII automaticamente. Ejemplo esta imagen de Simone Simons:
Como ven la cara esta formada con puras letritas de diferentes colores. Para activarlo pon en una consola lo siguiente:
cacaview nombre-archivo
Pero eso no es todo tambien podemos ver video!!!. Para esta tarea necesitamos instalar mplayer(personalmente instale mplayer-nogui) y realizar lo siguiente en consola:
mplayer -vo caca archivo_video ##este lo muestra a “color”
mplayer -vo aa archivo_video ##en blanco y negro
ahora una muestra del video de Metallica-Nothing Else Matters (color)

Ahora en Blanco y negro

pruebenlo y diviertanse un rato y que les sirva para la proxima que se les friegue el X server.
PD: Aca una super amiga ya caqueada!

Diciembre 22, 2007
Little endian vs Big Endian
El término inglés Endianness designa el formato en el que se almacenan los datos de más de un byte en una computadora.
Hay dos criterios que se han denominado little-endian y big-endian, cuyo nombre proviene de la novela Los viajes de Gulliver de Jonathan Swift en la que los habitantes de los imperios de Lilliput y Blefuscu libran una encarnizada guerra por una disputa sobre el lado por el que debían empezar a comerse los huevos.
El problema es similar a los lenguajes que se escriben de derecha a izquierda, como el árabe, o el hebreo, frente a los que se escriben de izquierda a derecha.
El sistema big-endian adoptado por Motorola entre otros, consiste en representar los bytes en el orden “natural”: así el valor hexadecimal 0×4A3B2C1D se almacenaría en memoria en la secuencia {4A, 3B, 2C, 1D}. En el sistema little-endian adoptado por Intel, entre otros, el mismo valor se almacenaría como {1D, 2C, 3B, 4A}.
Algunas arquitecturas de microprocesador pueden trabajar con ambos formatos (ARM, PowerPC, DEC Alpha, PA-RISC, MIPS), y a veces son referidas como sistemas middle-endian.
Ejemplo
Un código simple en lenguaje C para detectar si una máquina es little-endian o big-endian:
#include
int main(void)
{
int i = 1;
char *p = (char *) &i;
if ( p[0] == 1 )
printf(“Little Endian\n”);
else
printf(“Big Endian\n”);
return 0;
}
Diciembre 12, 2007
Aceleración con FPGAs: Tarjetas aceleradoras para PCI
Durante los últimos años en area de computacion reconfigurable se ha visto un gran desarrollo de lo que se llama “computadora reconfigurable”. El escenario más común es una computadora “host” (por ejemplo una Pentium 4) que tiene conectado a su bus PCI una tarjeta aceleradora basada en un FPGA.
Tomado de Wikipedia: http://es.wikipedia.org/wiki/FPGA
Una FPGA (del inglés Field Programmable Gate Array) es un dispositivo semiconductor que contiene bloques de lógica cuya interconexión y funcionalidad se puede programar. La lógica programable puede reproducir desde funciones tan sencillas como las llevadas a cabo por una puerta lógica o un sistema combinacional hasta complejos sistemas en un chip (w:en:System-on-a-chip).
Las FPGAs se utilizan en aplicaciones similares a los ASICs sin embargo son más lentas, tienen un mayor consumo de potencia y no pueden abarcar sistemas tan complejos como ellos. A pesar de esto, las FPGAs tienen las ventajas de ser reprogramables (lo que añade una enorme flexibilidad al flujo de diseño), sus costes de desarrollo y adquisisión son mucho menores para pequeñas cantidades de dispositivos y el tiempo de desarrollo es también menor.
Una excelente característica de los últimos FPGAs de Xilinx es lo que se conoce como reconfiguración dinámica parcial que es la capacidad de configurar una sección del FPGA mientras que el resto del FPGA sigue operando. Las regiones del FPGA que no son reconfiguradas seguirán operando ininterrupidamente.
Las computadoras reconfigurables permite desarrollar aplicaciones que requieren una ruta de datos o “datapath” cambiante (reconfigurable) o una unidad aceleradora en HW implementada dentro del FPGA y a requerimientos de nuestra aplicación. Son comunes por ejemplo unidades en HW dedicadas implementadas en el FPGA para: cálculo de la transformada rápida de Fourier, multiplicación de matrices, clasificación de documentos, optimización por algoritmos genéticos, etc.
En la mayoría de las tarjetas aceleradores con FPGA disponibles en el mercado, el FPGA recibe y envía datos a la computadora host mediante FIFOs implementados usando memoria SDRAM dentro de la tarjeta. Los datos dentro de esta memoria SDRAM ubicada en la tarjeta son transferidos desde/hacia la memoria RAM de la computadora host mediante un controlador DMA que también disponen estas tarjetas aceleradores.
El uso de tarjetas aceleradoras basadas en FPGAs es la base de la computación reconfigurable y permite implementar aplicaciones que usan el concepto de codiseño HW/SW.
La idea central del codiseño HW/SW es partir nuestra aplicación para partes de ella se ejecuten en HW (en el FPGA) y otra partes en SW (compilado en C++ generalmente con las GNU classpath).
El parcionado de la aplicación no es una tarea sencilla, en general se usan dos alternativas: profiling o un compilador especializado.
Usar profiling es una técnica muy conocida en desarrollo de SW asi que no comentaremos mucho. La idea es que nuestros procedimientos que veamos que consumen más tiempo en el computador los implementaremos en HW en el FPGA. Esto es un paso directo.
El uso de compiladores que automáticamente hagan este particionado es un tema reciente y de ardua investigación actual. La aplicación que estamos desarrollando se debe presenta escrita en lenguaje C/C++ a un compilador especializado para computadoras reconfigurables.
El compilador actua en primera instancia como cualquier compilador, tiene un scanner, un parser y un analizador semantico. Aqui al igual que un compilador normal construye una representación intermedia que comprende: la lista de instrucciones, el grafo de dependencias y el grafo de control de flujo.
Sin embargo el compilador no seguirá con el siguiente paso de generar código máquina ya que deberá decidir que instrucciones o bloque de instrucciones (tasks) serán ejecutados en HW o en SW.
Para que el compilador pueda hacer decidir donde mapear la “task” o tarea se requiren modelos de predicción que estimen cuanto requerira la “task” en ser ejecutada en SW o HW.
Estos modelos son muy complicados de formular. Por ejemplo uno de los modelos mas precisos que existen, llamado cycle-accurate-model implica un tiempo de simulación extremadamente grande y actualmente ha sido descartado su uso porque implicaría un tiempo de compilación demasiado grande.
Aparte de los modelos de predicción necesitamos del uso de técnicas de optimización. El problema de mapeo de las tareas o “tasks” de la aplicación en un conjunto o “set” de recursos de HW disponibles (procesador, FPGA, memoria, buses de comunicacion) pertenece al grupo de problemas conocidos como NP (no polinomiales) por los cuales no se conoce una solución deterministica no exponencial (combinatoria). En la practica, estos problemas son generalmente resueltos con algoritmos aleatorios o heuristicas como algoritmos geneticos y principalmente “enfriamiento simulado” (simulated annealing). En general el problema de desarrollar un compilador para computadoras reconfigurables tiene mucho interes actualmente.
Adicionalmente y regresando al HW, la rama FX de las familias de FPGAs Virtex 2 Pro, Virtex 4 y Virtex 5 de Xilinx vienen con procesadores PowerPC 405 incrustados o “embedded” dentro del FPGA. Estos procesadores ya vienen incorporados dentro del layout VLSI fisico del FPGA.
El numero de procesadores PowerPC que vienen varía entre 1 a 4 actualmente asi que también podemos hacer aplicaciones que requieran procesamiento paralelo usando estos procesadores.
Aqui les adjunto un diagrama de bloques que muestra los buses principales de una tarjeta aceleradora de FPGAs con un FPGA Virtex II Pro que viene con un PowerPC 405. Veanse que cada bus tiene su “arbitrer” que administra cual de los dispositivos conectados al bus puede tomar el control de este. Hacer que la arbitración del bus no dependa del procesador PowerPC por ejemplo permite el uso de acceso directo a memoria (DMA).
Nota: En este diagrama de bloques todo lo que esta dentro del FPGA se supone que ha sido instanciado por el diseñador digital. Dentro del FPGA el PowerPC viene como un “core” desconectado. Todo el resto del FPGA son celdas logicas que deben ser configuradas para determinar que función desempeñaran. Y que podemos utilizar para implementar los bus requeridos.
Programación en C++: Para que sirven los datos miembro static?
Ciertos miembros de una clase pueden ser declarados como static. Los miembros static tienen algunas propiedades especiales.
En el caso de los datos miembro static sólo existirá una copia que compartirán todos los objetos de la misma clase. Si consultamos el valor de ese dato desde cualquier objeto de esa clase obtendremos siempre el mismo resultado, y si lo modificamos, lo modificaremos para todos los objetos.
Por ejemplo:
#include
using namespace std;
class Numero {
public:
Numero(int v = 0);
~Numero();
void Modifica(int v);
int LeeValor() const { return Valor; }
int LeeCuenta() const { return Cuenta; }
int LeeMedia() const { return Media; }
private:
int Valor;
static int Cuenta;
static int Suma;
static int Media;
void CalculaMedia();
};
Numero::Numero(int v) : Valor(v) {
Cuenta++;
Suma += Valor;
CalculaMedia();
}
Numero::~Numero() {
Cuenta–;
Suma -= Valor;
CalculaMedia();
}
void Numero::Modifica(int v) {
Suma -= Valor;
Valor = v;
Suma += Valor;
CalculaMedia();
}
// Definición e inicialización de miembros estáticos
int Numero::Cuenta = 0;
int Numero::Suma = 0;
int Numero::Media = 0;
void Numero::CalculaMedia() {
if(Cuenta > 0) Media = Suma/Cuenta;
else Media = 0;
}
int main() {
Numero A(6), B(3), C(9), D(18), E(3);
Numero *X;
cout << “INICIAL” << endl;
cout << “Cuenta: ” << A.LeeCuenta() << endl;
cout << “Media: ” << A.LeeMedia() << endl;
B.Modifica(11);
cout << “Modificamos B=11″ << endl;
cout << “Cuenta: ” << B.LeeCuenta() << endl;
cout << “Media: ” << B.LeeMedia() << endl;
X = new Numero(548);
cout << “Nuevo elemento dinámico de valor 548″ << endl;
cout << “Cuenta: ” << X->LeeCuenta() << endl;
cout << “Media: ” << X->LeeMedia() << endl;
delete X;
cout << “Borramos el elemento dinámico” << endl;
cout << “Cuenta: ” << D.LeeCuenta() << endl;
cout << “Media: ” << D.LeeMedia() << endl;
cin.get();
return 0;
}
Observa que es necesario declarar e inicializar los miembros static de la clase, esto es por dos motivos. El primero es que los miembros static deben existir aunque no exista ningún objeto de la clase, declarar la clase no crea los datos miembro estáticos, en necesario hacerlo explícitamente. El segundo es porque no lo hiciéramos, al declarar objetos de esa clase los valores de los miembros estáticos estarían indefinidos, y los resultados no serían los esperados.
En el caso de la funciones miembro static la utilidad es menos evidente. Estas funciones no pueden acceder a los miembros de los objetos, sólo pueden acceder a los datos miembro de la clase que sean static. Esto significa que no tienen puntero this, y además suelen ser usadas con su nombre completo, incluyendo el nombre de la clase y el operador de ámbito (::).
Por ejemplo:
#include
using namespace std;
class Numero {
public:
Numero(int v = 0);
void Modifica(int v) { Valor = v; }
int LeeValor() const { return Valor; }
int LeeDeclaraciones() const { return ObjetosDeclarados; }
static void Reset() { ObjetosDeclarados = 0; }
private:
int Valor;
static int ObjetosDeclarados;
};
Numero::Numero(int v) : Valor(v) {
ObjetosDeclarados++;
}
int Numero::ObjetosDeclarados = 0;
int main() {
Numero A(6), B(3), C(9), D(18), E(3);
Numero *X;
cout << “INICIAL” << endl;
cout << “Objetos de la clase Numeros: “
<< A.LeeDeclaraciones() << endl;
Numero::Reset();
cout << “RESET” << endl;
cout << “Objetos de la clase Numeros: “
<< A.LeeDeclaraciones() << endl;
X = new Numero(548);
cout << “Cuenta de objetos dinámicos declarados” << endl;
cout << “Objetos de la clase Numeros: “
<< A.LeeDeclaraciones() << endl;
delete X;
X = new Numero(8);
cout << “Cuenta de objetos dinámicos declarados” << endl;
cout << “Objetos de la clase Numeros: “
<< A.LeeDeclaraciones() << endl;
delete X;
cin.get();
return 0;
}
Observa cómo hemos llamado a la función Reset con su nombre completo. Aunque podríamos haber usado “A.Reset()”, es más lógico usar el nombre completo, ya que la función puede ser invocada aunque no exista ningún objeto de la clase.
Programación en C++: Usando valores de retorno constantes para proteger datos de la clase
Esta es una propiedad que nos será muy útil en la depuración de nuestras clases. Además proporciona ciertos mecanismos necesarios para mantener la protección de los datos.
CASO 1: CUANDO DEVOLVEMOS VALORES DE DATOS MIEMBRO DE NUESTRA CLASE
Cuando una función miembro no modifique el valor de ningún dato de la clase, podemos y debemos declararla como constante. Esto no evitará que la función intente modificar los datos del objeto; a fin de cuentas, el código de la función lo escribimos nosotros; pero generará un error durante la compilación si la función intenta modificar alguno de los datos miembro del objeto.
Por ejemplo:
#include
using namespace std;
class Ejemplo2 {
public:
Ejemplo2(int a = 0) : A(a) {}
void Modifica(int a) { A = a; }
int Lee() const { return A; }
private:
int A;
};
int main() {
Ejemplo2 X(6);
cout << X.Lee() << endl;
X.Modifica(2);
cout << X.Lee() << endl;
cin.get();
return 0;
}
Para experimentar, comprueba lo que pasa si cambias la definición de la función “Lee()” por estas otras:
int Lee() const { A++; return A; }
int Lee() const { Modifica(A+1); return A; }
int Lee() const { Modifica(3); return A; }
Verás que el compilador no lo permite.
Evidentemente, si somos nosotros los que escribimos el código de la función, sabemos si la función modifica o no los datos, de modo que en rigor no necesitamos saber si es o no constante, pero frecuentemente otros programadores pueden usar clases definidas por nosotros, o nosotros las definidas por otros. En ese caso es frecuente que sólo se disponga de la declaración de la clase, y el modificador “const” nos dice si cierto modifica o no los datos del objeto.
Valores de retorno constantes
CASO 2: CUANDO DEVOLVEMOS PUNTEROS A ESTRUCTURAS DE DATOS DE NUESTRA CLASE
Otra técnica muy útil y aconsejable en muchos casos es usar valores de retorno de las funciones constantes, en particular cuando se usen para devolver punteros miembro de la clase.
Por ejemplo, supongamos que tenemos una clase para cadenas de caracteres:
class cadena {
public:
cadena(); // Constructor por defecto
cadena(char *c); // Constructor desde cadena c
cadena(int n); // Constructor para cadena de n caracteres
cadena(const cadena &); // Constructor copia
~cadena(); // Destructor
void Asignar(char *dest);
char *Leer(char *c) {
strcpy(c, cad);
return c;
}
private:
char *cad; // Puntero a char: cadena de caracteres
};
Si te fijas en la función “Leer”, verás que devuelve un puntero a la cadena que pasamos como parámetro, después de copiar el valor de cad en esa cadena. Esto es necesario para mantener la protección de cad, si nos limitáramos a devolver ese parámetro, el programa podría modificar la cadena almacenada a pesar de se cad un miembro privado:
char *Leer() { return cad; }
Para evitar eso podemos declarar el valor de retorno de la función “Leer” como constante:
const char *Leer() { return cad; }
De este modo, el programa que lea la cadena mediante esta función no podrá modificar ni el valor del puntero ni su contenido. Por ejemplo:
class cadena {
…
};
…
int main() {
cadena Cadena1(“hola”);
cout << Cadena1.Leer() << endl; // Legal
Cadena1.Leer() = cadena2; // Ilegal
Cadena1.Leer()[1] = ‘O’; // Ilegal
}
Programación en C++: cuando usar funciones inline
A menudo nos encontraremos con funciones miembro dentro de una clase que estamos definiendo cuyas definiciones son muy pequeñas.
En estos casos suele ser interesante declararlas como inline. Cuando hacemos eso, el código generado para la función cuando el programa se compila, se inserta en el punto donde se invoca a la función, en lugar de hacerlo en otro lugar y hacer una llamada.
Esto nos proporciona una ventaja, el código de estas funciones se ejecuta más rápidamente, ya que se evita usar la pila para pasar parámetros y se evitan las instrucciones de salto y retorno. También tiene un inconveniente: se generará el código de la función tantas veces como ésta se use, con lo que el programa ejecutable final puede ser mucho más grande.
Es por esos dos motivos por los que sólo se usan funciones inline cuando las funciones son pequeñas. Hay que elegir con cuidado qué funciones declararemos inline y cuales no, ya que el resultado puede ser muy diferente dependiendo de nuestras decisiones.
Hay dos maneras de declarar una función como inline.
La primera forma es la siguiente: las funciones que se definen dentro de la declaración de la clase son inline implícitamente. Por ejemplo:
class Ejemplo {
public:
Ejemplo(int a = 0) : A(a) {}
private:
int A;
};
En este ejemplo hemos definido el constructor de la clase Ejemplo dentro de la propia declaración, esto hace que se considere como inline. Cada vez que declaremos un objeto de la clase Ejemplo se insertará el código correspondiente a su constructor.
Si queremos que la clase Ejemplo no tenga un constructor inline deberemos declararla y definirla así:
class Ejemplo {
public:
Ejemplo(int a = 0);
private:
int A;
};
Ejemplo::Ejemplo(int a) : A(a) {}
En este caso, cada vez que declaremos un objeto de la clase Ejemplo se hará una llamada al constructor y sólo existirá una copia del código del constructor en nuestro programa.
La otra forma de declarar funciones inline es hacerlo explícitamente, usando la palabra reservada inline. En el ejemplo anterior sería así:
class Ejemplo {
public:
Ejemplo(int a = 0);
private:
int A;
};
inline Ejemplo::Ejemplo(int a) : A(a) {}
Diciembre 11, 2007
Truco para Bash: Encontrar los 20 archivos más grandes dentro de un directorio recursivamente
Con frecuencia queremos saber cuales son los archivos mas pesados (grandes) que tenemos en un directorio para saber si alguno podemos borrar para salvar espacio.
Un comando muy util para el Bash de Linux o Cygwin seria:
$ find . -printf “%kKb \”%p\”\n”|sort -nr|head -20
Explicación paso a paso…
find . -printf “%kKb \”%p\”\n”
Busca todo imprimiendo primero el tamaño en Kb %k, seguido del nombre (incluyendo el path) %p
Luego:
sort -nr
Ordena los resultados numericamente -n, y en reversa (descendente) -r
Finalmente, lista los primero 20:
head -20
Conclusion, busca e imprime los 20 archivos mas grandes.
Diciembre 10, 2007
Programación en Windows: Usar SQLite con Visual C++ 2005
Veremos aqui como usar SQLite con Visual C++. Lo haremos de una forma parecida a como hicimos con el port de pthreads para Windows.
1. Bajate el codigo fuente para sqlite3 de: http://www.sqlite.org/download.html
Necesitaremos el archivo: sqlite-source-3_3_7.zip Extrae estos archivos en un directorio temporal.
De esta carpeta copia el archivo sqlite3.h dentro del directorio \include del Visual C++.
2. Bajate la DLL de SQLite 3 de: http://www.sqlite.org/download.html
Necesitaremos el archivo: sqlitedll-3_3_7.zip. Extrae el contenido en un directorio temporal.
El contenido es:
sqlite3.dll
sqlite3.def
Igual que como hicimos con Pthread, copia la DLL en C:\Windows\system32
4. No tenemos hasta ahora el archivo LIB para el linker, de modo que necesitamos generar uno a partir del archivo .DEF que tenemos.
Inicia la ventana del Visual Studio 2005 Command Prompt. Ve a la carpeta donde tienes descomprimido el archivo .LIB y alli tipea:
LIB /DEF:sqlite3.def
Este comando te generara los siguientes archivos:
sqlite3.exp
sqlite3.lib
Listo
Ya tenemos el archivo .lib que necesitamos. Copia este archivo sqlite3.lib al directorio \lib del Visual C++ 2005.
5. Ya puedes crear proyectos usando SQLITE3 en Visual C++. Pero al igual que con la API pthread necesitas decirle al linker que use sqlite3.lib
Asi que ve al Menu Proyecto->Properties->Linker->Input->Additional dependencies y añade ’sqlite3.lib’
Aqui pongo un codigo de ejemplo en que se ve como crear una tabla, insertar datos en ella y realizar una consulta:
#include <cstdio>
#include “sqlite3.h”
using namespace std; //para printf en _c_stdio, esto es c++
int imprime_resultado(void* paque,int n, char** re,char** nomcol){
//El primer argumento de esta funcion es el 4 de la exec
//n es el numero de filas devueltas por la query
//re el contenido de la fila
//nomcol el nombre de la columna
printf(“El %s del componente es: %s\n”,nomcol[0],re[0]);
return 0;
}
int imprime_resultado2(void* paque,int n, char** re,char** nomcol){
printf(“El DNI y el nombre del componente: %s, %s\n”,re[0],re[1]);
return 0;
}
int main(int argc, char* argv[]) {
//creamos una variable del tipo de bases de datos.
sqlite3* db;
//establecemos conexión
sqlite3_open(“basedepruebas.db”,&db);
//comprobamos errores
if (sqlite3_errcode(db)!=0){
printf(“%s\n”,sqlite3_errmsg(db));
}
else{
//Creamos una tabla
sqlite3_exec(db,”create table hdlorean(dni integer not null,”
“nombre varchar(30) default ‘ ‘ not null,equipo varchar(30) default ‘ ‘ not null,”
//como esta sentencia no generara resultados el tercer argumento lo ponemos a null.
“primary key (dni));”,NULL,NULL,NULL);
//comprobamos errores.
if (sqlite3_errcode(db)!=0){
printf(“%s\n”,sqlite3_errmsg(db));
}
//introducimos varias filas
sqlite3_exec(db,”insert into hdlorean values (1,’XX’,'db’);”,NULL,NULL,NULL);
//comprobamos errores.
if (sqlite3_errcode(db)!=0){
printf(“%s\n”,sqlite3_errmsg(db));
}
sqlite3_exec(db,”insert into hdlorean values (2,’ZZ’,'db’);”,NULL,NULL,NULL);
if (sqlite3_errcode(db)!=0){
printf(“%s\n”,sqlite3_errmsg(db));
}
sqlite3_exec(db,”insert into hdlorean values (3,’YY’,'core’);”,NULL,NULL,NULL);
if (sqlite3_errcode(db)!=0){
printf(“%s\n”,sqlite3_errmsg(db));
}
sqlite3_exec(db,”insert into hdlorean values (4,’WW’,'core’);”,NULL,NULL,NULL);
if (sqlite3_errcode(db)!=0){
printf(“%s\n”,sqlite3_errmsg(db));
}
//realizamos una select que solo devolvera un resultado.
sqlite3_exec(db,”select nombre from hdlorean where dni=1;”,imprime_resultado,NULL,NULL);
if (sqlite3_errcode(db)!=0){
printf(“%s\n”,sqlite3_errmsg(db));
}
//realizamos una select que devolvera varios resultados.
sqlite3_exec(db,”select nombre from hdlorean where equipo=’core’;”,imprime_resultado,NULL,NULL);
if (sqlite3_errcode(db)!=0){
printf(“%s\n”,sqlite3_errmsg(db));
}
//realizamos una select que devuelve varias columnas y varios resultados
sqlite3_exec(db,”select dni,nombre from hdlorean where equipo=’db’;”,imprime_resultado2,NULL,NULL);
if (sqlite3_errcode(db)!=0){
printf(“%s\n”,sqlite3_errmsg(db));
}
}
//cerramos conexión
sqlite3_close(db);
return 0;
};
El proyecto compilara sin problemas, si tienen por alli ya alguna base de datos .db de SQLite podran entonces empezar a usarla sin problemas y el programa tambien portable a Linux Ubuntu
Programacion en Windows: Usar Pthreads con Visual C++ 2005
Dado que generalmente usando Visual C++ para programar en Windows aplicaciones que despues compilare para Linux, necesito algunos ports para Windows de librerias o APIs usadas en programacion en Linux.
Algunos ports para Windows que son muy utiles tener a mano aparte del GTK+ y del gtkmm son pthreads (POSIX threads) y SQLite3.
Los ports de estas APIs trabajan perfectamente con la mayoría de los compiladores C/C++ para Windows como el MinGW del Code::Blocks y el Visual C++ 2005.
En general muchas APIs de Linux pueden usarse en Windows siempre y cuando tengamos:
1. Los archivos .h de cabecera de la API que los copiamos en el directorio /include del Visual C++ 2005
2. Los archivos .lib de la API que son la libreria estatica donde estan implementadas las funciones definidas en los archivos de cabecera. Estos archivos se pueden copiar en el directorio /lib del Visual C++ 200. Necesitamo estos archivos para que el linker sepa de donde copiar codigo fuente (de estas .lib) para ponerlo dentro de nuestro ejecutable al momento de generarlo.
3. Los archivos .DLL de la API que son las librerias dinamicas que es lo que necesitamos para llevar nuestro proyecto a cualquier maquina (hacerlo portable), se pueden copiar en:
C:\Windows\system32
La mayoria de los ports para Windows de las APIs de Linux son compilados usando Visual C++ de modo que las DLLs que traen con ellos dependen de la MSVCRT.DLL (Microsoft Visual C++ Runtime) del Visual C++ 6.
En este caso veremos como como usar pthreads con Visual C++.
1. Primero necesitamos descargar el port de Pthreads para Windows de:
http://sourceware.org/pthreads-win32/
El archivo que baje fue: pthreads-2005-03-08.exe
2. Descomprime este archivo. En el interior encontraras un directorio llamado Pre-built. Dentro hay 2 directorios, uno con los archivos de cabecera /include y otro con las librerias para el enlazador o linker /lib.
3. Copia los 3 archivos .h que estan en el directorio include (pthread.d, semaphore.h, sched.h) dentro del directorio /include del Visual C++
4. Copia el archivo de libreria pthreadVC1.lib que esta dentro del directorio /lib al directorio /lib del Visual C++
5. Copia el archivo DLL pthreadVC1.dll que tambien esta dentro del directorio /lib al directorio C:\Windows\system32
6. Ahora ya estas listo para hacer una aplicacion usando pthreads en Visual C++ 2005. Lo unico que falta es que debes poner en propiedades de tu proyecto:
Menu Project ->Properties->Linker-> Input
Debes poner: pthreadVC1.dll
OJO: Debes hacer esto para ambas versiones del proyecto, tanto la Debug que usas para debugging como la Release que es la que vas a distribuir.
De forma que el linker o enlazador encontrara un lugar donde ya estan compiladas las funciones definidas en pthread.h y copiara desde esta LIB el codigo objeto compilado dentro de tu programa ejecutable y podras generarlo.
Bueno espero que les sea de ayuda para compilar sus aplicaciones de Linux en Visual C++ 2005.
El codigo fuente que uso es el mismo que presente en un post anterior. Prueba con este codigo y funcionara perfectamente
http://pintucoperu.wordpress.com/2007/12/06/pthreads-ventajas-y-limitaciones-de-usar-compartida/
Usando C/C++ para un calculo sencillo de PI
Una forma simple de calcular el valor aproximado de pi=3.1416 es el que usa la serie:
Pi = 4 [ 1 – 1/3 + 1/5 – 1/7 + 1/9 … + ((-1)n)/(2n+1)]
Podemos escribir un programa muy sencillo en C++ para calcular el valor aproximado de Pi usando esta serie, definitivamente no sera la mejor solucion ya que podriamos parelelizar este calculo (con MPI o mejorar la precision y velocidad)
#include <stdlib.h>
double pi_over_4(int n)
{
double iter=3;
int eveodd=1;
double pi=0.;
for(pi=1.; n>0; n–, iter+=2., eveodd=!eveodd)
{
if(eveodd)
pi-=1./iter;
else
pi+=1./iter;
}
return pi;
}
Los estados de un proceso en UNIX y Linux
La idea principal de un sistema multiproceso, tal y como es UNIX, es que el sistema operativo gestione los recursos disponibles (memoria, CPU, etc) entre los procesos que en ese momento trabajan en el sistema, de tal forma que, para ellos, el sistema se comporte como si fuera monousuario. Así que, en un sistema monoprocesador, la CPU se reparte entre los procesos que se tengan en ese momento. Como es lógico, sólo un proceso puede estar ejecutándose, los demás estarán esperando para poder ocupar la CPU, esta forma de operar recibe el nombre de ejecución entrelazada.
A continuación, se enumeran los distintos estados en los que se puede encontrar un proceso en este tipo de sistemas:
- Preparado (R).- Proceso que está listo para ejecutarse. Simplemente está esperando a que el sistema operativo le asigne un tiempo de CPU.
- Ejecutando (O).- Sólo uno de los procesos preparados se está ejecutando en cada momento (monoprocesador).
- Suspendido (S).- Un proceso se encuentra suspendido si no entra en el reparto de CPU, ya que se encuentra esperando algún tipo de evento (por ejemplo, la recepción de una señal software o hardware). En cuanto dicho evento se produce, el proceso pasa a formar parte del conjunto de procesos preparados.
- Parado (T).- Un proceso parado tampoco entra en el reparto de CPU, pero no porque se encuentre suspendido esperando algún evento. En este caso, sólo pasarán a estar preparados cuando reciban una señal determinada que les permita continuar.
- Zombie (Z).- Todo proceso al finalizar avisa a su proceso padre, para que éste elimine su entrada de la tabla de procesos. En el caso de que el padre, por algún motivo, no reciba esta comunicación no lo elimina de la tabla de procesos. En este caso, el proceso hijo queda en estado zombie, no está consumiendo CPU, pero sí continua consumiendo recursos del sistema.
![]() |
Un ejemplo que permite aclarar la diferencia entre procesos suspendidos y procesos preparados es el siguiente: se tiene un proceso que debe esperar a que el usuario introduzca un valor por teclado. En principio, pueden proponerse dos soluciones diferentes:
- Espera activa.
El proceso está codificado utilizando un bucle, en el cual sólo se comprueba el valor de una variable para saber si el usuario ha introducido el valor o no. En este caso, el proceso es un proceso preparado que está consumiendo CPU para realizar la comprobación de la variable continuamente. - Interrupción.
El proceso está codificado de forma que el proceso se suspende. De esta forma, ya no consume CPU porque no tiene que comprobar en cada ejecución del bucle si el usuario ha introducido un valor. Sólo espera a que sea el propio usuario el que le “avise” y lo despierte para, así, pasar a estar preparado.
Comparando un codigo simple con GTK: C#, Java y C++
Aqui discutiremos un poco acerca de la implementación del mismo código en C#, Java y C++. Un buen amigo Igor nos presento en una reunion este codigo para que lo portaramos a Java o C++: (e
VERSION EN C# Y MONO
using System;
using Gtk;
class TestGtkSharp
{
public static void Main()
{
new TestGtkSharp();
}
public TestGtkSharp()
{
Application.Init();
Window w = new Window(“Informatica Nicaragua”);
Button b = new Button(“Click me!!!”);
w.DeleteEvent += delegate {
Application.Quit();
};
int i = 0;
b.Clicked += delegate {
Console.WriteLine(“i es ahora: ” + ++i);
};
w.Add(b);
w.ShowAll();
Application.Run();
}
}
Lo compilamos con:
mcs -out:testgtk.exe -pkg:gtk-sharp-2.0 TestGtkSharp.cs
y ejecutamos con:
mono testgtk.exe
Aqui el reto de Igor era usar algo parecido al “delegate” de C#, sin embargo no existe este concepto en C++ ni Java (“delegate” que si existe algo parecido en Python o Ruby no es a mi criterio una ventaja salvo tal vez simplificar la programacion).
VERSION EN JAVA
Para compilar un programa en Java que use GTK, necesitaremos algunos paquetes en nuestro Ubuntu (instalandolos con apt-get):
sudo apt-get install libgtk-java libglib-java libgnome-java libgconf-java
y el codigo quedaria algo asi:
Reto.java
public class Reto{
int i;
public Reto() {
Window w = new Window(“Informática Nicaragua”);
Button b = new Button(“Click me!”);
i = 0;
b.addListener(new ButtonListener() {
public void buttonEvent(ButtonEvent e) {
if (e.isOfType(ButtonEvent.Type.CLICK)) {
System.out.println(“variable i es ahora: ” + ++i);
}
}
});
w.addListener(new LifeCycleListener() {
public void lifeCycleEvent(LifeCycleEvent arg0) {}
public boolean lifeCycleQuery(LifeCycleEvent arg0) {
Gtk.mainQuit();
return false;
}
});
w.add(b);
w.showAll();
}
public static void main(String[] args) {
Gtk.init(args);
new Reto();
Gtk.main();
}
}
C++ EN VISUAL C++ Y CON EL GNU CLASSPATH
Ahora me tocaba mi turno para programar. Decidi implementar tambien el programa en C++ (en el Visual C++ (lastima que sea adicto al Intellisense) aunque tambien compila con el GNU classpath de Linux):
Esta aplicacion haria lo mismo en C++ que la aplicacion propuesta en Java y C# (al menos en el comportamiento)
#include <iostream>
#include <gtkmm.h>
using namespace std;
using namespace Gtk;
using namespace sigc;
int i;
void boton1_clicked();
int main(int argc, char* argv[]) {
Main programita(argc,argv);
Window ventanita;
ventanita.set_title(“Demo de Gtkmm y C++”);
Button boton1(“Clickeame!”);
boton1.signal_clicked().connect(ptr_fun(&boton1_clicked));
ventanita.add(boton1);
ventanita.resize(250,30);
ventanita.show_all_children();
Main::run(ventanita);
return 0;
};
void boton1_clicked() {
cout<<”i tiene ahora un valor de: “<<++i<<endl;
};
El archivo lo podemos llamar prueba_boton.cpp
Si usamos Linux y GCC, se compila con:
g++ prueba_boton.cpp -o prueba_boton.x -O2 $(pkg-config –cflags –clibs gtkmm-2.4)
Como vemos para que boton1_clicked() vea a la variable i, no hay otra opcion que declararla como variable global (no hay nada parecido al “delegate” que presume tanto C#, que no es una ventaja, como expondre mi postura en otro post)
Ahora este programa que pongo tiene varios errores (no de programacion pero si una grandisimo de calidad en caso que querramos usar algo parecido a este codigo para programacion orientada a objetos),
Lo primero es que en C++ no hay garbage collector como en Java. Java trata en el fondo cada variable como un puntero y cuando el objeto ya no es usado ese puntero es dereferenciado y de esa forma recuperamos memoria de la “heap”.
En este caso los widgets “Windows” y “Button” que hemos usado han sido declarados como una variable (no es un puntero como en Java o C#) de modo que no son creados y destruidos dinamicamente.
La razon de este problema es que C++ no tiene garbage collector asi que tendremos que explicitamente crear y destruir objetos de la heap a mano en nuestro codigo.
Decidi como siguiente paso hacer el programa usando creacion y destruccion de los objetos en la “heap” y para eso ya use un poquito de programacion orientada a objetos a C++ (es decir la ventana de dialogo es una clase y los widgets que ponemos los definimos como datos miembros de esta clase: lo que se llamaria composicion en UML).
Aqui viene primero mi archivo de cabecera de definicion de la clase:
PruebaBotonDlg.h
#ifndef PRUEBA_BOTON_PRUEBABOTONDLG_H
#define PRUEBA_BOTON_PRUEBABOTONDLG_H
#include <gtkmm.h>
//llamamos al namespace Gtk para evitar usar
//definiciones de clase tipo Gtk::button
using namespace Gtk;
class PruebaBotonDlg : public Gtk::Window
{
public:
PruebaBotonDlg();
virtual ~PruebaBotonDlg();
private:
//signal handlers
virtual void on_boton1_clicked();
//Usamos agrupacion de widgets que hay en esta ventana
Button* boton1;
//la variable i
int i;
};
#endif
Ahora la implementacion:
PruebaBotonDlg.cpp
#include “PruebaBotonDlg.h”
#include <iostream>
using namespace std;
using namespace Gtk;
PruebaBotonDlg::PruebaBotonDlg() {
//definimos el valor de i
i = 0;
//creamos objetos de widgets hijos
boton1 = new Button(“Clickeame!”);
set_title(“Ejemplo de C++ y Gtkmm”);
resize(300,25);
boton1->signal_clicked().connect(sigc::mem_fun(*this,
&PruebaBotonDlg::on_boton1_clicked));
add(*boton1);
show_all_children();
};
PruebaBotonDlg::~PruebaBotonDlg() {
delete boton1;
};
void PruebaBotonDlg::on_boton1_clicked() {
cout<<”i es ahora: “<<++i<<endl;
};
Y finalmente la aplicacion en si:
PruebaBotonApp.cpp
#include “PruebaBotonDlg.h”
using namespace Gtk;
int main(int argc, char* argv[]) {
Main miaplicacion(argc, argv);
PruebaBotonDlg* midialogo;
midialogo = new PruebaBotonDlg();
//muestra la ventana de dialogo y retornamos
//cuando la cerramos
Main::run(*midialogo);
delete midialogo;
return 0;
};
Despues de compilar el programa (presentado en un post anterior) y ejecutarlo obtenemos:
En mi implementacion al igual que la hecha por mi otro amigo en Java, “i” es definida como variable miembro de la clase PruebaBotonDlg. No hay uso de “delegates” como en C#.
Lo que hace dificil este codigo vs otras opciones como C# o Java es en este caso es la complejidad.
Primero que no tenemos forma de esconder el uso de punteros al programador (operadores * y &) y lo otro es la necesidad de tener que crear y destruir a mano los objetos de la ” heap” con instrucciones “new” y “delete” de C++ por causa de no tener un garbage collector como en Java o Mono.
Personalmente me quedo con Java y C++ ya que el creador/editor de GUIs del Netbeans crea una clase en base al diseño del formulario que hemos creado. Esta idea es la misma que usa el editor de formularios Glade (para C++) y wxSmith (parte del CodeBlocks) para wxWidgets.
No nos esconde que el editor de formularios creara una clase en base al layout que creamos graficamente y que los controles que colocamos sobre el formulario seran datos miembros de esta clase (lo que llamamos composicion en UML). C# esconde este concepto de modo que mal acostumbra al programador aunque se gana tiempo en desarrollo.
Diciembre 8, 2007
Instalar impresora Samsung SCX 4100 en Ubuntu
Esta Multifuncional es una de las mas económicas, en cuanto a consumo de toner y recarga. Hasta ahora la recomiendo para quienes tienen una oficina o cibercafe.
Una de las particularidades de esta multifuncional es que es compatible con LINUX, eso fué lo que me agrado.
En el manual del producto menciona que es compatible con:
- Redhat 7.1 and above
- Linux Mandrake 8.0 and above
- SuSE 7.1 and above
- Caldera OpenLinux 3.1 and above
- Turbo Linux 7.0 and above
- Slackware 8.1 and above
Software Requerido
- Linux Kernel 2.4 or higher
- Glibc 2.2 or higher
- CUPS
- SANE (yo use xsane)
Pero no se que pasa que los drivers que vienen en el CD original no funcionaron al menos en las distribuciones en que probe, Fedora core y Ubuntu Dapper. Y pues para mi era necesario tener la impresora y el scaner funcionando en mi distro.
Aquí presento la solución.
1. Hay que bajar el último pack en la zona de drivers de la página de samsung para Linux.
http://www.samsung.com/us/support/download/supportDown.do?group=printersmultifunction&group_cd=&type=printersmultifunction&type_cd=06010000&subtype=monochromelasermultifunctionprintersfaxes&subtype_cd=06010300&model_nm=SCX-4100&dType=D&vType=L&mType=UM&model_cd=&menu=%2Fsupport%2Fdownload%2FsupportDown.do&prd_ia_cd=06010300&acc_ia_fl=N&disp_nm=SCX-4100&isEqualsY=
Yo estoy usando la versión 20060719092647968_UnifiedLinuxDriver.tr.gz
2. Descomprimir el archivo, hay que observar que dentro del comprimido hay un directorio llamado Linux.
3. Abrimos una consola y nos situamos en la carpeta Linux y ejecutamos el archivo install.sh de la siguiente forma.
$ sudo ./install.sh
Vas a ver algunos errores que según no encuentra un dispositivo, no te preocupes ahí es donde esta el detalle tu sigue con la instalación.
Si pruebas en este momento la impresora veras que no responde, esto se debe porque Linux no encuentra en que puerto esta dado de alta la impresora, vamos a decircelo de la siguiente forma.
1. Vamos a añadir 2 líneas en el archivo 45-libsane.rules en el directorio /etc/udev/rules.d abre una consola y ejecuta.
$ sudo gedit /etc/udev/rules.d/45-libsane.rules
2. Agrega las líneas antes de la línea LABEL=”libsane_rules_end”
# Samsung|SCX-4100
SYSFS{idVendor}==”04e8″, SYSFS{idProduct}==”3413″, MODE=”664″, GROUP=”scanner”
Aquí le estamos dando ha linux el uso del scanner para todos los usuarios y diciendole como se llama el dispositivo correcto.
3. Ahora vamos abrir el archivo
$ sudo gedit /etc/udev/rules.d/60-symlinks.rules
Añadimos las siguientes lineas al final del documento.
# Create symlink for usb printer to /dev/usb/lp*
BUS==”usb”, KERNEL==”lp[0-9]*”, SYMLINK+=”usb/%k”
Aquí estamos direccionando la impresora al puerto USB correcto y que es asignado al archivo /dev/usblp0 de esta forma ya tu entorno linux debe reconocer tu impresora y por xsane tambien.
4. Solo falta reiniciar servicios para que surtan efecto.
$sudo /etc/init.d/udev restart
$sudo /etc/init.d/cupsys restart
Y listo puedes configurar desde menu/sistema/administracion/impresoras la multifuncional.
FORMA ALTERNATIVA (MANUAL Y MÁS SEGURA)
Lo que necesitas antes de empezar es:
- Tener instalado CUPS, obviamente. Y si quieres escaner, tambien SANE.
- El driver de Samsung, descárgalo de su página:
http://www.samsung.com/us/support/download/supportDown.do?group=printersmultifunction&group_cd=&type=printersmultifunction&type_cd=06010000&subtype=monochromelasermultifunctionprintersfaxes&subtype_cd=06010300&model_nm=SCX-4100&dType=D&vType=L&mType=UM&model_cd=&menu=%2Fsupport%2Fdownload%2FsupportDown.do&prd_ia_cd=06010300&acc_ia_fl=N&disp_nm=SCX-4100&isEqualsY=
Ok, si utilizaste la copia que tengo hay que descomprimir el tar.gz en algun lugar y luego entrar, puesto que el formato y localizacion de los archivos cambian a veces en los paquetes de drivers de samsung, te tocará buscarlos a mano (Puedes usar el comando de consola >> find -iname). Ten cuidado, si tienes AMD64 requieres los de 64 bits, y si no, usa los otros, no los mezcles o no funciona.
- Copiar el ppd para CUPS. Hay que hacerle gzip pues el que viene esta descomprimido. Luego metelo a
- /usr/share/cups/model/samsung/scx4100.ppd.gz
- Busca los siguientes archivos y metelos en los lugares indicados:
- /usr/lib/cups/backend/mfp
- /usr/lib/cups/filter/rastertosamsungspl
- /usr/lib/libmfp.so.1.0.1
- El archivo de SANE para el escaner:
- /usr/lib/sane/libsane-samsung_scx4100.so.1.0.1
- Acomoda las cosas:
- ldconfig
Listo, ahora usa el Administrador de Impresoras (gnome-cups-manager) para instalar tu impresora. Si aún te pide driver usa el PPD que comprimiste scx4100.ppd.
Instala la impresora con el gnome-cups-manager como con cualquier otra impresora. Ten cuidado de ponerle “Usar Filtrado Local”.
Disfruta de tu SCX4100.
Con esto ya podrías imprimir, si queremos usar también el escáner habrá que hacer unos pasos adicionales:
1. Asegúrate de tener instalado Sane (programa para escaneo) y un programa para edición de gráficos como el Gimp con el que llamaras a sane:
>>sudo apt-get install libsane libsane-extras sane sane-utils xsane gimp gimp-data
2. Vamos a añadir 2 líneas en el archivo 45-libsane.rules en el directorio /etc/udev/rules.d abre una consola y ejecuta.
$ sudo gedit /etc/udev/rules.d/45-libsane.rules
3. Agrega las líneas antes de la línea LABEL=”libsane_rules_end”
# Samsung|SCX-4100
SYSFS{idVendor}==”04e8″, SYSFS{idProduct}==”3413″, MODE=”664″, GROUP=”scanner”
Aquí le estamos dando ha linux el uso del scanner para todos los usuarios y diciendole como se llama el dispositivo correcto.
4. Ahora vamos abrir el archivo
$ sudo gedit /etc/udev/rules.d/60-symlinks.rules
Añadimos las siguientes lineas al final del documento.
# Create symlink for usb printer to /dev/usb/lp*
BUS==”usb”, KERNEL==”lp[0-9]*”, SYMLINK+=”usb/%k”
Lo que pasa es que la impresora SCX4100 se mapea como dispositivo en /dev/usb/lp0 y esto es un problema ya que el archivo de configuración para Sane busca al escáner en /dev/usblp0
Aquí estamos direccionando (usando un enlace simbólico) la impresora al puerto USB correcto y que es asignado al archivo /dev/usblp0 de esta forma ya tu entorno linux debe reconocer tu impresora y por xsane tambien.
5. Solo falta reiniciar servicios para que surtan efecto.
$sudo /etc/init.d/udev restart
6. Busca el archivo smfp.conf (configuración del escáner de la SCX4100 para Sane) en mi caso estaba en: noarch/at_root/etc/sane.d/smfp.conf y pégalo como:
- /etc/sane.d/smfp.conf
7. Edita el archivo:
>>sudo gedit /etc/sane.d/dll.d/libsane-extras
Y añade al final la línea “smfp”. Graba el archivo y sal.
8. Con esto ya podrías escanear pero solo como usuario “root”, pero lo que queremos es que todos los usuarios del grupo scanner puedan escanear.
Asi que añadimos a nuestro usuario al grupo escaner:
>>sudo adduer <miusuario> scanner
9. El problema siguiente que tenemos es que la librería libmpf.so.1.0.1 que nos viene con las últimas versiones del driver de Samsung trata de instalar un módulo para el kernel mfpport.ko Lo malo es que Samsung no tiene a Ubuntu en su lista de distribuciones soportadas, así que al detectar a Ubuntu no instala este módulo del kernel.
Si ejecutamos xsane con nuestro usuario para tratar de escanear, tendremos un mensaje de error:
>>xsane
insmod: can’t read ‘/lib/modules/2.6.17-14mdv/kernel/drivers/mfpportctrl/mfpport.ko‘: No such file or directory Segmentation fault …
Felizmente un amigo nos ha subido una versión parchada de esta librería que no requiere de este módulo mfpport.ko
Descarga el archivo que adjunto a continuación y cámbiale la extensión a .tar.gz Luego lo descomprimes.
Busca en el directorio descomprimido el archivo libmpf.so.1.0.1 (hay dos versiones, una para i386 y la otra para x64). Copia la adecuada en /usr/lib
Puedes sobreescribir la antigua /usr/lib/libmpf.so.1.0.1 o hacer una copia de seguridad.
Por dejar todo bien, haz un ldconfig.
>>ldconfig
Ahora ya podrías llamar a tu escáner usando xsane
Para esto tipea en consola (y a disfrutar):
>>xsane
Diciembre 7, 2007
Refrescar las filas de un Treeview en gtkmm
Este post me pareció muy útil y por eso se los adjunto, el link es el que pongo:
Tomado de: http://jubuntu.blogsome.com/category/programacion/linux/gtkmm/
Cuando estamos trabajando con el TreeView de las gtkmm y necesitamos actualizar varias filas de manera simultanea desde diferentes hilos nos encontramos con que las dichosas filas no se actualizan de la manera correcta. Es más se comportan de forma totalmente incoherente.
A los programadores que vengan de Windows, como yo, es posible que en un principio les resulte extraño el comportamiento del TreeView. En realidad es bastante extraño, ya que en Windows, no hace falta hacer ningún tipo de gestión especial, como mucho llamar a Invalidate o a Redraw, pero por desgracia el Gtk TreeView, no tiene implementados este tipo de métodos.
Cuando queremos actualizar la vista del TreeView desde un hilo, difrente al hilo principal, debemos indicarselo al sistema explicitamente. Ya que si no lo hacemos, el sistema no se enterará de los cambios que ha realizado nuestro hilo, y por lo tanto no los reflejerá en pantalla.
Para mostrar una posible forma de actualizar el TreeView, desde diferentes threads, he hecho un pequeño programa basado en uno de los ejemplos que hay en la página oficial de gtkmm. El cuerpo del programa sería el siguiente:
file: main.cpp
#include <gtkmm /main.h>
#include <glibmm .h>
#include “listprogress.h”
#include
int main(int argc, char **argv)
{
Glib::thread_init();
gdk_threads_init();Gtk::Main kit(argc, argv);
ListProgress window;gdk_threads_enter();
Gtk::Main::run(window);
gdk_threads_leave();
return 0;
}
En el main, debemos llamar a las funciones Glib::thread_init() y gdk_threads_init(), la primera, nos habilita el funcionamiento de los threads dentro de nuestra aplicación, y la segunda está relacionada con el manejo de pantalla por parte de los diferentes hilos.
Después llamamos a Gtk::Main kit(argc, argv) que inicializa las gtk y construimos un nuevo objeto ListProgress window, el cuál está definido en los archivos posteriores.
Por último sólo nos queda pasarle el control a nuestro nuevo objeto Gtk::Main::run(window), pero como sabemos que el contenido del TreeView va a ser modificado desde diferentes threads, tendremos que incluir la llamada entre estás funciones que nos indican que vamos a manejar el uso de la pantalla; gdk_threads_enter() y gdk_threads_leave().
A continuación mostraré, la definición y declaración del objeto ListProgress.
file: listprogress.h
#ifndef LISTPROGRESS_H
#define LISTPROGRESS_H
#include
#include “threadprogress.h”
class ModelColumns : public Gtk::TreeModel::ColumnRecord
{
public:
ModelColumns()
{
add(m_col_id); add(m_col_name); add(m_col_number); add(m_col_percentage);
}
Gtk::TreeModelColumn m_col_id;
Gtk::TreeModelColumn m_col_name;
Gtk::TreeModelColumn m_col_number;
Gtk::TreeModelColumn m_col_percentage;
};
class ThreadProgress;
class ListProgress: public Gtk::Window
{
public:
ListProgress();
~ListProgress();protected:
virtual void on_button_quit();
protected:
ThreadProgress* mTrhead1;
ThreadProgress* mTrhead2;
ThreadProgress* mTrhead3;
Gtk::VBox m_VBox;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::TreeView m_TreeView;
Glib::RefPtr m_refTreeModel;
Gtk::HButtonBox m_ButtonBox;
Gtk::Button m_Button_Quit;
public:
ModelColumns m_Columns;
};
#endif
file: listprogress.cpp
#include “listprogress.h”
ListProgress::ListProgress() : m_Button_Quit(”Salir”)
{
set_title(”Gtk::TreeView (ListStore) Ejemplo”);
set_border_width(5);
set_default_size(400, 200);add(m_VBox);
m_ScrolledWindow.add(m_TreeView);
m_ScrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);m_VBox.pack_start(m_ScrolledWindow);
m_VBox.pack_start(m_ButtonBox, Gtk::PACK_SHRINK);m_ButtonBox.pack_start(m_Button_Quit, Gtk::PACK_SHRINK);
m_ButtonBox.set_border_width(5);
m_ButtonBox.set_layout(Gtk::BUTTONBOX_END);
m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this, &ListProgress::on_button_quit) );m_refTreeModel = Gtk::ListStore::create(m_Columns);
m_TreeView.set_model(m_refTreeModel);Gtk::TreeModel::Row row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 1;
row[m_Columns.m_col_name] = “Billy Bob”;
row[m_Columns.m_col_number] = 10;
row[m_Columns.m_col_percentage] = 0;
mTrhead1 = new ThreadProgress(&row, &m_Columns, 20000);
mTrhead1->Start();row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 2;
row[m_Columns.m_col_name] = “Joey Jojo”;
row[m_Columns.m_col_number] = 20;
row[m_Columns.m_col_number] = 0;
mTrhead2 = new ThreadProgress(&row, &m_Columns, 40000);
mTrhead2->Start();row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 3;
row[m_Columns.m_col_name] = “Rob McRoberts”;
row[m_Columns.m_col_number] = 30;
row[m_Columns.m_col_percentage] = 0;
mTrhead3 = new ThreadProgress(&row, &m_Columns, 60000);
mTrhead3->Start();m_TreeView.append_column(”ID”, m_Columns.m_col_id);
m_TreeView.append_column(”Nombre”, m_Columns.m_col_name);
m_TreeView.append_column_numeric(”Número formateado”, m_Columns.m_col_number, “%010d”);Gtk::CellRendererProgress* cell = new Gtk::CellRendererProgress;
int cols_count = m_TreeView.append_column(”Porcentage”, *cell);
Gtk::TreeViewColumn* pColumn = m_TreeView.get_column(cols_count – 1);
if(pColumn)
pColumn->add_attribute(cell->property_value(), m_Columns.m_col_percentage);for(guint i = 0; i < 2; i++)
{
Gtk::TreeView::Column* pColumn = m_TreeView.get_column(i);
pColumn->set_reorderable();
}show_all_children() ;
}
ListProgress::~ListProgress()
{
delete mTrhead1;
delete mTrhead2;
delete mTrhead3;
}
void ListProgress::on_button_quit()
{
hide();
}
Como podeis ver, el código es casi identico al del ejemplo de las gtkmm, sólo tiene una pequeña modificación que consiste en la incursión del objeto ThreadProgress, que describiré a continuación. Este objeto es el encargado de crear los diferentes threads, y de actualizar los datos del TreeView desde los nuevos hilos creados. Su código es el siguiente:
file: threadprogress.h
#ifndef THREADPROGRESS_H
#define THREADPROGRESS_H#include
#include
#include “listprogress.h”class ModelColumns;
class ThreadProgress{
public:
ThreadProgress(Gtk::TreeModel::Row*, ModelColumns*, int);
~ThreadProgress();
protected:
Glib::Thread* thread;
Gtk::TreeModel::Row row;
ModelColumns* column;
int delay;
void main();
public:
void Start();
};
file: threadprogress.cpp
#include “threadprogress.h”
#include
#includeThreadProgress::ThreadProgress(Gtk::TreeModel::Row* row, ModelColumns* colum, int delay)
{
this->row = *row;
this->column = colum;
this->delay = delay;
}
ThreadProgress::~ThreadProgress()
{
}
void ThreadProgress::Start()
{
thread = Glib::Thread::create(sigc::mem_fun(*this, &ThreadProgress::main), true);
if (!thread)
{
std::cout < < “Error al crear el thread” << std::endl;
exit(1);
}
}
void ThreadProgress::main()
{
for(int i = 0; i<=100; i++)
{
gdk_threads_enter();
row[column->m_col_percentage] = i;
gdk_flush();
gdk_threads_leave();
usleep(delay);
}
return;
}
En el constructor inicializamos los campos correspondientes al retardo que le ponemos a cada hilo, la fila a la que representa la barra de progreso del hilo, y la estructura ModelColumns, que nos permitirá acceder a cualquiera de las columnas del TreeView.
El método Start, simplemente se encarga de lanzar el nuevo hilo;
thread = Glib::Thread::create(sigc::mem_fun(*this, &ThreadProgress::main), true);
Y en el método main, que ya estará en un nuevo contexto de ejecución, es donde realizamos la actualización de las barras de progreso con el retardo indicado en el constructor.
void ThreadProgress::main()
{
for(int i = 0; i< =100; i++)
{
gdk_threads_enter();
row[column->m_col_percentage] = i;
gdk_flush();
gdk_threads_leave();
usleep(delay);
}
return;
}
Seguro que hay otras maneras de actualizar las columnas del ListStore desde diferentes hilo, ésta es solo una de las posibles implementaciones, y no creo que sea la mejor. Si alguno conoceis una manera alternativa, ruego que la comenteis.
Os dejo una bonita captura de pantalla del pequeño programita que he hecho.

Programación en Windows: Usar gtkmm con Visual C++
Aqui veremos como usar Visual C++ 2005 para programar con gtkmm.
Primero suponemos que hemos seguido las instrucciones para instalar GTK+ (Development) y gtkmm en Windows (eso lo comentamos en otro post):
http://pintucoperu.wordpress.com/2007/12/01/programacion-en-cygwin-bajo-windows-1-gtk-y-gtkmm/
Ahora trataremos de usar el port de las librerías gtkmm en Visual C++. La razón de hacer esto es el excelente soporte para autocompletado que tiene Visual C++ y su debugger (el cual soporta la STL, standard template library, cosa que carece el GDB).
1. REQUISITOS PREVIOS
Instala el GTK+ Development Environment y el GTKMM Development Libraries para Windows. Ve mi post anterior:
http://pintucoperu.wordpress.com/2007/12/01/programacion-en-cygwin-bajo-windows-1-gtk-y-gtkmm/
2. CREANDO EL PROYECTO
Crea un proyecto en Visual C++ 2005 y elige proyecto de consola. Acepta la opción donde te dice precompiled header (muchos programadores de Linux no querran hacer esto, pero acelerara el proceso de compilado de tus aplicaciones).
3. CORRIGE LA FUNCION DE ENTRADA MAIN( )
Ahora en tu aplicación debes cambiar donde dice:
int _tmain(int argc, _TCHA* argv[])
por:
int main(int argc, char* argv[])
La razón es que Visual C++ define lo llamado “translated main” que es mejor en Windows pero si lo usamos nuestro proyecto no sería portable a Linux que es lo que pretendemos.
4. CORREGIR stdafx.h
Este paso es importante, corregiremos la cabecera de “precompiled headers” para hacerla compatible para una futura compilación en Linux.
Debemos eliminar las directivas:
#pragma
y la línea que llama a la cabecera #include <tchar.h>
Nuestro stdafx.h debería lucir algo asi:
5.AÑADE CODIGO A TU APLICACION PARA UNA VENTANA DE EJEMPLO
Ahora nos resta añadir código a nuestra aplicación para que haga una prueba definiendo una ventana de GTK+.
6. AÑADIR ARCHIVO DE CONFIGURACIÓN DE DEPENDENCIAS DE GTKMM
Este paso es muy parecido al uso del pkg-config del MSYS, pero en el formato de Visual C++. En este caso deberas usar las “hojas de propiedades” que vienen con el gtkmm. Estos archivos de extensión .vsprops se encuentran en el directorio MSVC donde instalaste las librerías GTK+ y gtkmm (por tanto por defecto sería C:\GTK\MSVC):
Son dos archivos:
gtkmm-2.4d.vsprops
gtkmm-2.4.vsprops
Deberas copiarlos a la carpeta principal de tu proyecto.
(este articulo aun no esta terminado, la guía completa pueden verla en:
http://www.gtkmm.org/docs/gtkmm-2.4/docs/tutorial/html/sec-visual-studio-new-project.html)
Introducción a la STL string en C++: conversion entre char* y string
Aqui posteo un listado de un programa para manipular strings o cadenas de caracteres (char *) y realizar conversiones entre ambos tipos de datos.
Aqui empieza el programa, he tratado de comentarlo para hacer mas facil su comprension:
#include “stdafx.h” //precompiled header del Visual C++
#include <iostream>
#include <string>
#include <sstream> //para usar ostringstream
#include <vector>
using namespace std;
template < class T >
string ToString(const T &arg)
{
ostringstream out;
out << arg;
return(out.str());
}
int main(int argc, char* argv[])
{
char *cadena1;
cadena1 = new char[30];
string cadena2;
strcpy(cadena1,”El numero”);
strcat(cadena1,” 40″);
//conversion de char* a string
//string solo acepta crear en base a un char*
cadena2 = string(cadena1) + ” ” + ToString(50);
//conversion de string a char*
//no hacer asignacion directa de punteros porque
//string::c_str devuelve const char* asi que habra
//error si se modificara cadena1
strcpy(cadena1, cadena2.c_str());
//Definimos vector de cadenas
vector<string> SS;
SS.push_back(“El numero es 10″);
SS.push_back(“El numero es 20″);
SS.push_back(“El numero es 30″);
//podemos empujar una cadena char* al final del vector
SS.push_back(cadena1);
//podemos empujar una string
SS.push_back(cadena2);
cout<<”Bucle por indice:”<<endl;
for (unsigned int i=0; i < SS.size(); i++) {
cout<< SS[i] << endl;
};
cout<<”Bucle usando iterador:”<<endl;
vector<string>::iterator ii;
for (ii = SS.begin(); ii < SS.end(); ii++) {
cout<< *ii << endl;
};
cin.get();
return 0;
}
Espero que les sea de utilidad
Bug en Debug del Visual C++ 2005
El siguiente error de VC++2005 Express y Full de vez en cuando. Ocurre que a veces cuando estamos debugueando el compilador no rehace el archivo .res de nuestro proyecto, es un bug de VC++ lo unico que debemos hacer es borrar todo lo que haya en /debug/* (especialmente el archivo .res)
Error al iniciar la aplicación porque no se encontró MSVCP80D.dll. La reinstalación de la aplicación puede solucionar el problema.
En realidad el archivo MSVCP80D.dll no existe físicamente como un archivo. Así que ni siguiera intentes buscarlo, no esta en tu HD, ni tampoco hay de donde descargar el archivo. Cosas raras que hace MS…
Tambien se presenta cuando distribuimos un ejecutable compilado en modo Debug a alguien que no tiene VC++. Solo debemos compilar en modo Release y listo.
Diciembre 6, 2007
INVITACION PARA LOS AMIGOS PROGRAMADORES: Multiplicación de matrices
EN ESTE POST INVITO A TODOS LOS AMIGOS PROGRAMADORES QUE VISITAN ESTE BLOG A COLABORAR CON UN PROGRAMA CORTO Y HACER SUS COMENTARIOS.
LES AGRADEZCO MUCHISIMO SI PUEDEN COLABORAR Y SON MUY BIENVENIDOS.
Cuando queremos acelerar una aplicación mediante paralelización, debemos tener en cuenta si el algoritmo puede ser particionado para ejecutarse en paralelo. Al particionar un algoritmo se puede seguir dos criterios:
- Particionado por datos
- Particionado por tareas
En el caso del particionado por datos, la idea es partir el algoritmo en base a como manipula la estructura de datos. Si vemos que la manipulación de los datos se puede dividir según la región de la estructura de datos sobre la que operamos entonces es posible usar paralelización.
Uno de los casos en que se usa paralelización por particionamiento de los datos es en multiplicación de matrices. Porque?
Amigos si se deciden a colaborar, les pido que posteen:
- Comenta las estructuras de datos usadas y como particionaste el algoritmo.
- Código del programa comentado y especificando que API están usando.
- Benchmarking (tiempo de ejecución de su programa y memoria consumida): inicializando los elementos de sus matrices como números enteros con signo aleatorios. Pueden hacer las pruebas con N>20 (sugeridos N=20,40,60,80 y 100) y repetirlas para cada valor de N para tomar tiempo de ejecución promedios.
- Especificaciones de su máquina o … “cluster”
(procesador, memoria, sistema operativo) - Compilador que han usado (OJO: especificar si dieron directivas especiales -O2, -O3, /SS2, etc)
Estan muy cordialmente invitados y se les agradece de antemano mucho su participación.
El problema puede enunciarse como:
Realizar una implementación de multiplicación de matrices (NxN) usando alguna de las siguientes APIs en lenguaje C o C++ o C# o Java.
- Programa secuencial simple
- Hilos en GNU/Linux
- Procesos en GNU/Linux
- OpenMP (en C++ o C)
- MPI (Message Passing Interface, en C)
- Hilos en Windows
- Procesos en Windows
La multiplicación de matrices consiste en:

Notas para programadores principiantes y con ganas
1. Tengan en cuenta que al trabajar con hilos se puede utiliza memoria compartida para la comunicación entre hilos. Para comunicar los procesos pueden buscar pipe o mkfifo para crear un canal de comunicación entre el proceso hijo y el proceso papá.
Acelerando aplicaciones con hilos: introducción a la API PThread
Todos los UNIX (y GNU/Linux, que es un derivado) son sistemas operativos multitarea. Bajo el sistema tradicional UNIX, todos los procesos que corren en nuestra computadora son creados de la misma manera, se crean al llamar a la función del sistema operativo (system call) fork( ). Una instrucción comunmente usada en añadido a fork( ) es execl( ).
La llamada a sistema Fork( ) produce una segunda copia del proceso llamante o padre. Esta segunda copia es idéntica al programa original, excepto que el valor que retorna fork( ) en el proceso hijo es igual a “1″, mientras que el valor que retorna fork( ) en el padre es el PID (process identifier) del proceso hijo.
De esta manera el uso normal de fork es:
[tareas que hace el padre]
ppid = fork( );
if (ppid < 0) {
error_de_funcion_fork( );
} else {
if (ppid == 1) funcion_del_hijo( );
else funcion_del_padre( );
};
Notemos que esto trabaja solo porque fork( ) retorna dos copias completamente independientes del proceso original. Cada proceso tiene su propio espacio de nombres (namespace, que ya comentamos antes en otro post), con su propia copia de variables, las cuales son completamente independientes de las mismas variables en el otro proceso. Las únicas excepciones son algunas variables SysV IPC, pero esto es un caso especial.
Esta independencia, si bien nos provee protección de memoria y por tanto estabilidad, causa problemas cuando queremos tener múltiples procesos que trabajen en la misma tarea o problema. Es cierto que podemos usar tuberías (pipes) o SysV IPC, pero aún algunos serios problemas:
- El costo de cambiar entre múltiples procesos (lo que se conoce como “context switching“) es relativamente alto porque estamos involucrando al sistema operativo.
- Cada vez que usamos fork( ) o creamos un nuevo proceso, el sistema operativo tiene que crear un nuevo sistema de memoria virtual y un entorno para el nuevo proceso.
- Hay frecuentemente un límite en el número de procesos que el scheduler del sistema operativo puede manejar con eficiencia.
- Las variables de sincronización, que son compartidas entre múltiples procesos son típicamente lentas.
INTRODUCCION A LOS THREADS
Por estas razones, se desarrolla el concepto de “threads” o hilos. Los hilos comparten un espacio de memoria común y con frecuencia son programados (scheduled) dentro de un solo proceso, de forma que se evitan un montón de las ineficiencias de múltiples procesos.
Las características de un hilo son:
- Las operaciones sobre hilos incluyen creación, finalización, sincronización(unión o “join”, bloqueado o “blocking”), programación (“scheduling”), manejo de datos e interacción entre procesos.
- Un hilo no mantiene una lista de los hilos que el ha creado ni conoce al hilo que lo creó.
- Todos los hilos dentro de un proceso comparten el mismo espacio de memoria.
- Los hilos en un mismo proceso comparten:
- Instrucciones del proceso
- La mayor parte de los datos
- Archivos abiertos (descriptores o “descriptors”)
- Señales y manejadores de señales (“signal handlers”)
- El directorio actual de trabajo
- ID del usuario y del grupo
- Sin embargo cada hilo tiene un único:
- ID de hilo
- conjunto de registros (“register set”) y stack
- Stack para las variables locales y las direcciones de retorno de subrutina.
- Máscara de señal
- Prioridad
- Valor de retorno
- Las funcions de hilo pthread returnan “0″ si todo fue OK.
El concepto de hilos o “threads” fue originado en el modelo fork-join de la computación paralela. Un thread maestro corre serialmente hasta que se encuentra con una directiva y se produce una bifurcación con nuevos threads.
En UNIX y Linux (también hay un port para Windows) tenemos una API muy popular para crear aplicaciones con hilos que se llama POSIX Threads o pthreads.
VENTAJAS DE LOS THREADS
Los threads son efectivos para acelerar nuestra aplicación especialmente en sistemas multiprocesador o multi-core , donde el flujo del proceso pueden ser dividido para ser ejecutados en múltiples procesadores obteniendo aceleración mediante procesamiento paralelo o distribuído.
En sistemas uniprocesador, la ganancia de velocidad se puede obtener mediante el evitar la latencia en operaciones de entrada/salida y llamadas a sistema que pueden poner en pausa la ejecución de nuestro proceso. Por ejemplo, uno de nuestros hilos puede estar haciendo un cálculo mientras que el otro está esperando por entrada/salida o alguna otra latencia debido a llamada al sistema operativo.
Existen otras tecnologías para programación paralela como MPI (message passing interface), que sin embargo son usadas entornos de computación distribuída. Los threads están limitados a una sola computadora.
El uso de hilos es una opción lógica para obtener aceleración en sistemas multiprocesador con memoria-compartida como las placas dual-core o quad-core de Intel. Orientada específicamente a estos sistemas, ha sido desarrollado una API basada en el concepto de threads que se llama OpenMP.
En el caso de OpenMP, los threads son distribuídos y ejecutados en diferentes procesadores, reduciendo el tiempo de ejecución haciendo que mas ciclos de procesos estén disponibles por unidad de tiempo. Los resultados de cada ejecución de threads pueden luego ser combinados. Un usuario puede establecer el números de threads creados para regiones paralelas.
Sin embargo al agregar procesadores en un esquema de memoria-compartida se incrementa el tráfico en el bus del sistema (recordemos que tenemos una sola memoria RAM compartida entre todos los procesadores y solo uno puede acceder a la vez a esa memoria RAM), haciendo mas lento el tiempo de acceso a memoria y demorando la ejecución de un programa. Esta es la principal limitación para escabilidad en sistemas multiprocesador con memoria compartida como es el caso de OpenMP
UN PROGRAMA BASICO USANDO PTHREADS
La API pthreads esta disponible para UNIX y Linux (también hay un port para Windows). Para usarla debemos incluir la cabecera pthread.h en nuestro código fuente y decirle al compilador que haga enlace (linking) con la libreria estática pthread.
El comando para compilar sería algo parecido a:
gcc programa_con_pthreads.c -o programa_con_pthreads -lpthread
Aquí mostramos un programa básico con pthreads.
Observemos que los argumentos que recibe la función del thread es un puntero del tipo void* de modo que podemos pasar cualquier estructura de datos como argumento a la función del thread.
Observemos además que la función pthread_exit que está al final de la función que ejecuta el thread devuelve un puntero de tipo void**.
El programa lo llamamos ejemplo.cpp:
#include <iostream>
#include <pthread.h>
#include <string.h>
using namespace std;
#define MAX_THREADS 10
//Tabla con los identificadores de los threads
pthread_t tabla_thr[MAX_THREADS];
//Definimos un mutex en caso querramos serializar recursos //compartidos (en nuestro caso la consola) o para evitar race //conditions
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
int ultimothread;
//definimos la estructura de datos que usaremos para darle argumentos a la función del thread
typedef struct {
int id; //identificador del thread
char* cadena; //mensaje al thread
} thr_param_t;
thr_param_t param[MAX_THREADS];
void* funcion_thr(void *pointer)
{
//creamos un mutex ya que la consola es un recurso compartido y queremos bloquearla para que no la use otro thread
pthread_mutex_lock( &mutex1 );
thr_param_t* parametro;
parametro = (thr_param_t *) pointer;
cout<<parametro->cadena<<” leido por thread nro: “<<parametro->id<<endl;
//para saber cual fue el ultimo thread en ejecutarse
ultimothread++;
//liberamos el mutex ya que el recurso de la consola y la variable ultimothread ya no estan libres
pthread_mutex_unlock( &mutex1 );
//una vez terminado, devolvemos el valor
pthread_exit(&(parametro->id));
}
int main(void) {
int i, id;
//necesitamos definir un puntero tonto para dar argumentos y recibir datos al-hacia la función del thread
void *dummypointer;
ultimothread=0;
//Iniciando creacion de los threads
cout<<”Iniciando creacion de los threads:\n”;
for(i=0; i<MAX_THREADS; i++) {
param[i].cadena = strdup(“Hola Mundo!”);
param[i].id = i;
dummypointer = (void *) ¶m[i];
pthread_create(&tabla_thr[i],NULL, funcion_thr, dummypointer);
}
cout<<”Los threads fueron creados. Esperando que terminen.”<<endl;
//Recopilamos datos devueltos por los threads con //la función pthread_join
for(i=0; i<MAX_THREADS;i++) {
pthread_join(tabla_thr[i],&dummypointer);
id = *( (int *) dummypointer);
cout<<”El thread “<<i<<” devolvio “<<id<<endl;
};
cout<<endl<<”El numero de threads ejecutados fue: “<<ultimothread<<endl;
return 0;
};
Para compilar el programa usamos:
$g++ ejemplo.cpp -O2 -o ejemplo.exe
En mi caso al ejecutarlo obtengo:
$ ./ejemplo.exe
Iniciando creacion de los threads:
Hola Mundo! leido por thread nro: 0
Los threads fueron creados. Esperando que terminen.
Hola Mundo! leido por thread nro: El thread 10
devolvio 0
Hola Mundo! leido por thread nro: 2
El thread 1 devolvio 1
Hola Mundo! leido por thread nro: El thread 32
devolvio 2
Hola Mundo! leido por thread nro: El thread 43
devolvio 3
Hola Mundo! leido por thread nro: El thread 54
devolvio 4
Hola Mundo! leido por thread nro: El thread 65
devolvio 5
Hola Mundo! leido por thread nro: El thread 76
devolvio 6
Hola Mundo! leido por thread nro: 8
El thread 7 devolvio 7
Hola Mundo! leido por thread nro: El thread 98
devolvio 8
El thread 9 devolvio 9
El numero de threads ejecutados fue: 10
Espero que les sea de utilidad. Muchos saludos.






