Archivo para la categoría ‘Informática’

Implementación de la clase de sincronización Semaphore con la librería boost (y alguna cosa más)

Una de las primitivas clásicas de la sincronización de procesos e hilos —hablando de informática— es el semáforo. La diferencia entre este y el mutex es que el semáforo se puede bloquear y liberar desde diferentes hilos/procesos mientras que el mutex no; es para proteger una zona de código (el acceso a variables por un sólo hilo).

La librería boost —una de las mejores librerías generalistas para C++ que hay, por no decir la mejor— no hay un semáforo de este tipo propiamente dicho, sino que se usan variables de condición para implementarlo; o semáforos con nombre, aunque estos son más específicos para sincronización entre procesos en lugar de entre hilos. Esta implementación se basa mucho en el estándar POSIX de hilos (pthreads) pero orientándolo a objetos.

Es por esto que, para no tener que usar un variable de condición, un mutex y un contador cada vez que se quiere implementar un semáforo, se puede usar esta clase:

#include <boost/thread.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/thread/mutex.hpp>

class Semaphore {
	private:
    	mutable boost::mutex fMutex;
	    boost::condition_variable fCondVar;
    	unsigned long fCounter;

	public:
    	Semaphore(unsigned long counter)
          : fMutex(),
            fCondVar(),
            fCounter(counter)
        {
    	}

	    void post() {
    	    boost::mutex::scoped_lock lock(fMutex);
    	    ++fCounter;
    	    fCondVar.notify_one();
	    }

    	void wait() {
    	    boost::mutex::scoped_lock lock(fMutex);
    	    while(fCounter <= 0)
    	        fCondVar.wait(lock);
    	    --fCounter;
    	}

    	bool isLocked() const {
    		return fCounter == 0;
    	}
};

Un buen ejemplo de uso de un semáforo es implementar una cola de datos usada para el famoso problema del productor/consumidor, es decir, tenemos dos procesos, uno que produce datos y los mete en la cola, y otro que los consume sacándolos de dicha cola teniendo en cuenta que, cuando la cola está vacía, el consumidor está bloqueado esperando a que haya más valores.

Para ello, la implementación de la cola podría ser la siguiente:

#include <deque.hpp>

using std::deque;

template <typename T>
class Queue {
	private:
		deque<T> fQueue;
		mutable boost::mutex fAccessLock;
		mutable Semaphore fSem;

	public:
		Queue() : fQueue(), fAccessLock(), fSem(0) {}

		virtual ~Queue() {}

		virtual void push(T elem) {
			fSem.post();
			boost::mutex::scoped_lock lock(fAccessLock);
			fQueue.push_front(elem);
		}

		virtual T pop() {
			fSem.wait();
			boost::mutex::scoped_lock lock(fAccessLock);
			T elem = fQueue.back();
			fQueue.pop_back();
			return elem;
		}
};

Y el programa de prueba implementando dos funciones, una el productor y otra el consumidor, y usando hilos de boost sería:

#include <boost/ref.hpp>
#include <boost/date_time.hpp>
#include <iostream>

template <typename T>
void
producer(Queue<T>& q) {
	int counter = 0;
	int times = 5;
	while(!boost::this_thread::interruption_requested() && times-- > 0) {
		for(int i = 0; i < 5; i++) {
			cout << "producer: inserting " << counter << endl;
			q.push(counter++);
		}
		sleep(4);
	}
}

template <typename T>
void
consumer(Queue<T>& q) {
	while(!boost::this_thread::interruption_requested()) {
		cout << "consumer: extracting " << q.pop() << endl;
		boost::this_thread::sleep(boost::posix_time::milliseconds(100));
	}
}

int
main() {
	Queue<int> q(7);

	boost::thread p(producer,boost::ref(q));
	sleep(1);
	boost::thread c(consumer,boost::ref(q));

	p.join();
	c.join();

	return 0;
}

En este ejemplo, el hilo productor inserta cinco números cada 4 segundos (a ráfagas) mientras que el productor saca un número cada 100 milisegundos. Cuando la cola está vacía, gracias al semáforo el hilo consumidor espera hasta que el productor inserte más números, y así sucesivamente.

Pues sí, pues vale, muy bonito, pero ¿para qué se usa esta cola y el productor/consumidor? Pues, en informática, en multitud de software. Por ejemplo, en el reproductor multimedia que usáis todos los días. El hilo productor es el que hilo que lee del disco duro la música insertando los datos leídos en un buffer (en la cola), y el consumidor es el hilo que saca los datos de ese buffer, los decodifica y los envía a la tarjeta de sonido. Y con el vídeo funciona igual (aparte, claro, de los mecanismos de sincronización entre sonido y vídeo).

Pero en esta implementación hay una cosa que falta: el productor debería parar de insertar elementos cuando la cola esta llena hasta que el consumidor saque algún elemento. Pero esa implementación será para el próximo programa ;) .

11 características que Twitter debería tener

Mucha gente usa Twitter, esa red social tan “rara” que sólo permite 140 caracteres en cada mensaje. Y quizás sea por eso su gran éxito a pesar de que sus características técnicas no han cambiado desde… bueno, casi desde que se creó. Por eso creo que, desde el punto de vista de un usuario (no de un desarrollador), Twitter necesita, al menos, que se implementen estas 11 características:

  1. Edición de tuits: Sería bueno poder editar los tuits, al menos durante un tiempo después de la publicación, para corregir, por ejemplo, faltas de ortografía. No sería bueno poder editarlos siempre ya que con la sucesivas ediciones podrías hacer totalmente incoherente la conversión con el resto de usuarios.
  2. Bloqueo de usuarios sin necesidad de dejar de seguirlos: ¿Sabéis eso de “te sigo por compromiso”? Pues eso. Además, he visto algunos tuits donde ya se demanda esta característica.
  3. Omisión de tuits en el timeline que contengan un determinado hashtag: No estaría demás que se pudiera, en el TL principal, omitir de forma automática los tuits que contengan algún hashtag determinado, por eso de ser, en ocasiones, bastante cansinos.
  4. Poder recuperar todos tus tuits: En caso de tener un cliente de Twitter que no sea el de Web (y creo que con el de Web también), sólo podrás recuperar tus 3000 últimos tuits. Aunque el resto siguen en la base de datos, su API no soporta recuperar más de esta cantidad.
  5. Posibilidad de copia de seguridad de tus tuits: No hay ninguna forma de recuperar todos tus tuits si no es con aplicaciones externas. Y a esto hay que aplicarle el límite del punto anterior. Twitter debería implementar una forma de hacer copia de seguridad de tus tuits.
  6. Respuesta pública: Posibilidad de responder a un tuit de forma pública (que lo vean todos los usuarios que te siguen en lugar de los usuarios que te siguen y los que siguen al que respondes) para así no tener que poner un carácter antes del nombre de quien estás respondiendo y tener que desaprovecharlo.
  7. Implementación de un sistema para “leer más tarde”: Yo, al menos, uso los favoritos como marcadores para leer luego. No tengo ningún tuit favorito, simplemente en mi cliente del móvil los marco (los que tienen imágenes, alguna URL…) para leer cuando esté en un ordenador o tablet. Recordemos las limitaciones de las pantallas pequeñas y, sobre todo, las limitaciones de las conexiones 3G.
  8. Formato del texto: Esta quizás sería la más rompedora con la filosofía de Twitter, pero ayudaría mucho a expresar justamente lo que queremos decir —“emociones” incluidas— con, al menos, negrita, cursiva y tachado.
  9. Enlaces contextualizados: Los enlaces deberían ir como en HTML, enlazando un texto en lugar de que vaya la URL directamente. El contexto es muy importante en la Web y Twitter está haciendo un esfuerzo enorme —entiendo que inconscientemente— para eliminarlo. Lo mismo que los acortadores URL, donde se pierde toda la semántica de las URLs amigables. Pero ese es otro tema.
  10. Estadísticas: Sería buena idea que se proporcionaran estadísticas —principalmente informativas— de uso de Twitter, tanto de los usuarios (tuits por día, horarios de tuiteo, retuits…) como generales (tuis diarios, tuits diarios por países, usuarios activos en tiempo real…).
  11. Avance automático en el cliente Web: En el cliente Web, cuando sale el aviso de que hay nuevos tuits, al hacer clic, la página debería avanzar automáticamente hasta el último que has leído. Además, en la nueva interfaz, se debería marcar de alguna forma el último tuit que has leído (en la antigua se hacía).

Y una característica extra, la duodécima: más caracteres. Aquí hay muchísima controversia. Quizás, como he dicho antes, el éxito de Twitter sea precisamente el de tener un número de caracteres limitados. Pero, al menos a mi, muchas más veces de las que me gustaría se me quedan muy cortos para escribir lo que quiero decir. Y a veces no soy capaz de hacerle caso al famoso mensaje “Tu Tweet contiene mas de 140 caracteres. Tendrás que ser más ingenioso.”.

No estoy diciendo que se ponga un número ilimitado (o muy grande) de caracteres como ocurre con Google+ o Facebook, pero sí alguno más, por ejemplo —por aventurarme con un número— 200; o, mejor, 256 (28, por eso de ser friki :tongue: ). Por ejemplo, las notas de Menéame tienen 1000 caracteres y creo que la mayoría de ellas no pasan ni de los 500, pero cuando hay algo más largo que decir, se puede hacer sin ningún problema.

Pero bueno, esta es sólo mi opinión. Y vosotros ¿qué opináis de estas 11 características que Twitter debería tener? ¿Alguna más? ¿Alguna menos?

¿Pagarías por usar tu distribución de Linux favorita?

Me ha llamado mucho la atención esta encuesta del blog Usemos Linux: “¿Pagarías por usar tu distribución favorita de Linux?”.

Mi respuesta corta: No.

Mi respuesta larga: No porque todas las distribuciones usan el mismo software. Todas. Todas usan X Window System (de momento Wayland está en desarrollo). Todas usan Gnome o KDE con alguna disidente que usa Xfce, LXDE o similares. Todas tienen las mismas librerías. Todas tienen la misma arquitectura multimedia. Y, como no, todas usan, en esencia, el mismo Kernel.

¿Por qué pagar por algo que haga una empresa cuando cualquier otra empresa o particular lo puede hacer sin ningún coste? Incluso, si eres un poco aficionado al tema, te puedes hacer tu propia distribución sin más coste que tu tiempo. Y, ojo, que no estamos hablando del servicio técnico.

Yo, personalmente, pagaría por tener algo diferente (y mejor, se entiende). Algo que haya hecho una empresa para mejorar lo que hay en lugar de ser una mera agrupación de distintos elementos de software, todo con el mismo Kernel, y ponerle un nombre rimbombante.

Si Windows no es de mi agrado a nivel técnico (y lo uso como usuario), Linux tampoco lo es (y también lo uso como usuario). Ya sabéis que yo soy más de BeOS/Haiku :P .

C#

Por necesidades del servicio voy a empezar a programar en C#, así que me he leído un par de manuales y he practicado un poco con ello. Y, la verdad, no me ha convencido demasiado. Así que, por petición popular (si por una persona se le puede llamar “popular”), estas son algunas de las conclusiones que he sacado, aunque si eres muy fan de C# quizás no deberías seguir leyendo :P :

Declaración de tipos

Extrañamemente se usan formas diferentes para declarar lo mismo. Porque declarar una variable o un nuevo tipo de dato debería ser consistente. Por ejemplo, para declarar una variable se usa <tipo de dato> <identificador>;. En cambio, para declarar un alias de un tipo de dato (no hay nuevos tipos) se usa using <identificador> = <tipo de dato>;. Y, ya para rematar, para declarar un delegate se usa delegate <valor devuelto> <identificador> (<parámetros>);.

¿No sería más sencillo y consistente tener una sintaxis común? Por ejemplo: <tipo de dato> <descripción> <identificador>;, con lo que la declaración quedaría:

// Variable:
int i;

// Tipo de dato:
typedef int MyInt;

// Usando 'using' (sin introducir ninguna palabra reservada adicional):
using int as MyInt;

// Delegate:
delegate void(int) MyDelegate;

Con esto la sintaxis siempre es consistente. Tenemos a la izquierda los tipos y a la derecha, siempre, el nombre tanto de la variable como del tipo nuevo como del delegate (que no deja de ser un tipo nuevo).

Nuevos tipos de datos

En C#, al igual que en C++, no se pueden declarar nuevos tipos de datos. Sí, en serio. Lo que estáis pensando ahora mismo es que con typedef sí se puede… pues no. En realidad es un alias del tipo básico. En C++, este código:

typedef int myint_t;
void overloaded_function(int value);
void overloaded_function(myint_t value);  // Error de compilación.

Daría error de compilación porque las dos funciones son iguales. myint_t no es un tipo de datos nuevo, sino un alias de int. En C# ocurre lo mismo, sólo que en lugar de declarar los nuevos tipos con typedef (que no tiene esa palabra reservada) se usa using:

using myint_t = int;

class Main {
  void overloaded_funcion(int value) {...}
  void overloaded_funcion(myint_t value) {...}  // Error de compilación.
}

También se puede hacer alguna triquiñuela, como hacer una clase MyInt que herede de System.Int32. Pero ya habría que implementar ciertas cosas para que funcionase.

Los métodos y su tipo de acceso

Es bastante enrevesado el uso de virtual, override y new para controlar el polimorfismo de las clases. Por ejemplo, en el código:

class A {
	public virtual void Who() { Console.WriteLine("A"); }
}

class B : A {
	public override void Who() { Console.WriteLine("B"); }
}

class C : B {
	public new virtual void Who() { Console.WriteLine("C"); }
}

class D : C {
	public override void Who() { Console.WriteLine("D"); }
}

C c = new D();
c.Who();	// Escribe "D"; lógico ¿no?

A a = new D();
a.Who();	// ¡Escribe "B"! ¿Dónde está la lógica de funciones virtuales aquí?

Es obligatorio que en todos los métodos que se podrán sobreescribir en clases derivadas se ponga virtual; que en todos los métodos que sobreescriben se ponga override; y que si un método oculta a otro se ponga new.

¿No sería más sencillo que todos los métodos fuesen virtuales por defecto (porque cuando haces clases, lo más probable es que las vayas a heredar)? ¿No sería más lógico que cuando declaras un método con el mismo nombre en la clase base, automáticamente se sobreescriba el de la base y, en caso de querer llamar al método de la base, uses super, base o similar?

Por cierto, decidme un caso real donde se use esta funcionalidad.

El problema de la clase base frágil

En este manual se menciona el problema de la clase base frágil, que habla de que en Java podría suceder esto, teniendo en principio estas clases:

class BaseClass {
	public void CleanUp() {
		System.out.println("BaseClass.CleanUp()");
	}
}

class DerivedClass extends BaseClass {
	public void Delete() {
		System.out.println("DerivedClass.Delete()");
	}
	public void CleanUp() {
		Delete();
	}
}

public class Test {
	public static void main(String[] args) {
		BaseClass k = new DerivedClass();
		k.CleanUp();
	}
}

La salida de este programa sería:

$ javac Test.java && java Test
DerivedClass.Delete();
$

Si luego implementamos el método Delete() en la clase base de esta forma:

class BaseClass {
	public void CleanUp() {
		System.out.println("BaseClass.CleanUp()");
	}
	public void Delete() {
		System.out.println("Delete all the world!");
	}
}

Según el manual, ¡podríamos borrar el mundo!

Esto sería un verdadero problema… si existiese. Pero es muy fácil comprobar que no se produce. La salida con la clase modificada es la misma que sin ella. Sólo usando super.Delete() se podría borrar el mundo.

Ámbito global o local

En C#, si una clase oculta un espacio de nombres, para acceder a dicho espacio introduce una nueva palabra que no está reservada, pero que hay que recordar: global. Por ejemplo:

class Main {

  public class System {}

  static void Main(string[] args) {
    System.Console.WriteLine("¡Hola mundo!");  // Error: la clase 'System' oculta
                                               // el espacio de nombres 'System'.
    global::System.Console.WriteLine("Esto sí funciona.");
  }
}

¿No sería más sencillo, en lugar de introducir otro identificador nuevo, usar sólo el operador ::, como hace C++ o, incluso, usar el operador .?

Comportamientos extraños de las variables

Resulta que si declaras una variable de tipo delegate, esta se comporta como una lista de delegates que se llaman todos seguidos:


class Test {
	void Notify1(string text) {
		System.Console.WriteLine("Hello " + text);
	}
	void Notfiy2(string text) {
		System.Console.WriteLine("Goodbye " + text);
	}
}

Test test = new Test();

delegate void Notifier(string text);

Notifier notifier;
notifier = new Notifier(test.Notify1);
notifier += new Notifier(test.Notify2);

notifier("Manolito");

// La salida será:
// Hello Manolito
// Goodbye Manolito

¿Nos aclaramos? ¿Notifier es una variable o es una lista de variables? Y si todos los delegates asignados devuelven algún valor, sólo se devuelve el último ejecutado. ¿Y el resto?

Recorriendo listas

C# añade el foreach, que no es más que lo que se conoce como syntactic sugar para el bucle for de toda la vida evitando que el programador use variables índice:


string[] stringArray = new string[] { /* Inicialización */ };

foreach(var s in stringArray) {
  System.Console.WriteLn(s);
}

Pero no estoy criticando ese syntactic sugar, todo lo contrario, eso me parece muy bien. Cuanto menos código se escriba y haga más cosas, mejor.

Lo que ya no me parece tan bien es que en ese bucle foreach sólo se tenga acceso a los valores de la lista a recorrer y no se tenga acceso al índice de dicha lista. Sí, simplemente a la famosa i que se omite para que el programador escriba menos código.

Y por culpa de esto, en caso de necesitarla, pues tienes que declararla fuera e incrementarla manualmente. Vamos, con en un bucle for.

Anidamiento

No se permiten métodos anidados, es decir, declaración e implementación de métodos dentro de otros métodos. Sí, venga, vale, se puede “solucionar” mediante el uso de delegates, pero eso no es permitir métodos anidados, eso es una chapuza.

Y además, ya para rematar, tampoco se permite declarar estructuras ni clases dentro de métodos. Como está orientado a objetos, pues toda declaración adicional tiene que ir dentro de la clase.

Actualización 2011-12-05: C y C++ tampoco permiten métodos anidados como me indican en un comentario. Esto lo puse porque yo los he usado para resolver algún problema, sólo que no recuerdo ni el lenguaje ni el proyecto en el que lo hice. ¿Pascal quizás?

Atributos

Los atributos del lenguaje también son otra cosa que está muy bien dentro de un lenguaje. Permiten extender la funcionalidad del compilador, por ejemplo, para hacer compilación condicional, controlar entre diferentes versiones o arquitecturas, marcar elementos como obsoletos… pero, me pregunto, ¿por qué la sintaxis no es coherente con el resto del lenguaje y hay que añadir triquiñuelas del estilo [Conditional("Debug")] al código?

Conclusión

Después de despotricar un poco toca una pequeña conclusión:

Si Microsoft tuvo la oportunidad de crear un nuevo lenguaje de programación desde cero, para ellos solos, sin que nadie se pueda meter, sin que nadie le diera lecciones, con la posibilidad de innovar, de mejorar lo que había hasta el momento ¿por qué lo ha hecho tan retorcido e incoherente? Porque sí, en serio, es retorcido. Hasta C++ —que mira que a Bjarne Stroustrup se le fue la olla cuando lo diseñó— es más consistente y coherente que C#. C# parece un batiburrilo de Java, C++ y alguna funcionalidad adicional como los delegates que más que ser un lenguaje nuevo es lo mismo de siempre con otro nombre. Y propietario, claro.

Y, bueno, de la compilación a código intermedio en lugar de a código máquina directamente ya no hablamos. Eso es una decisión que han tomado pero que tampoco es que importe demasiado. Recordemos que esta generación de código es una de las fases de compilación que se puede cambiar sin afectar a la sintaxis.

Concluyendo, a mi, personalmente, no me convence en absoluto. Pero claro, si quieres hacer un proyecto decente para sistemas Windows, no queda otra que hacerlo en C# o pelearte con los punteros en C++.

O usar Pascal ;) .

P.D.: Yo, en realidad, soy más del lenguaje de programación D.

Representar un número en binario en C++

Puede que alguna vez en tu vida como desarrollador de software te veas en la necesidad de representar números en su forma binaria. Hay infinidad de algoritmos para ello, por eso no me voy a enrollar mucho. Aquí te muestro tres en C++:

La forma sencilla e intuitiva

template <typename T>
char*
ValueToBits(T value) {
	int length = sizeof(T)*8;
	char* result = new char[length];
	result[length] = '\0';
	for(int i = 0; i < length; i++) {
		result[length-1-i] = (value & (1 << i) ? '1' : '0');
	}
	return result;
}

Este algoritmo es fácilmente adaptable a C estándar. Basta con quitar la plantilla, sobrecargar esta función con más versiones, una por cada tipo de dato que se quiera convertir, y usar malloc() en lugar de new. Y, por supuesto, no hay que olvidarse de liberar la memoria del valor devuelto, o bien con delete[] si usas new, o bien con free() si usas malloc().

Usando std::strings

Este algoritmo es similar al anterior pero usando std::strings, con lo que evitamos el uso de char* y la reserva manual de memoria.

#include <string>

using std::string;

template <typename T>
const string
ValueToBits(T value) {
	string result;
	for(int i = 0; i < sizeof(T)*8; i++) {
		result = (value & (1 << i) ? "1" : "0") + result;
	}
	return result;
}

La forma elegante

Y ya que estamos en C++, ¿por qué no usar toda la potencia que nos proporciona la STL?

#include <bitset>

using std::bitset;

template <typename T>
const string
ValueToBits(T value) {
	return bitset<sizeof(T)*8>(value).to_string();
}

No me digáis que esta forma no es muy elegante ;) .

¿Qué significan los iconos de inicio de HaikuOS?

Iconos de inicio de HaikuOS

Cuando se inicia el sistema operativo Haiku se presentan una serie de iconos para indicar en qué estado del inicio nos encontramos. Estos iconos significan lo siguiente:

  • Haiku boot: Atom Inicializa los módulos del kernel.
  • Haiku boot: Disk with magnifier glass Monta el sistema de archivos principal en / (rootfs) y monta el sistema de archivos de los dispositivos en /dev (devfs).
  • Haiku boot: Plug-in card Inicializa el gestor de dispositivos.
  • Haiku boot: Disk with leaf Monta el disco del sistema (/boot).
  • Haiku boot: Chip Activa los módulos para controlar las características específicas de la CPU.
  • Haiku boot: Folder Finalización del inicio de los subsistemas (que cargan módulos del disco principal en /boot).
  • Haiku boot: Rocket Carga en memoria e inicia el script (BootScript) que carga los servidores de inicio como el app_server o el input_server.

En una máquina virtual con VirtualBox (en un equipo moderno, todo hay que decirlo) tarda algo menos de 10 segundos en iniciar el sistema. Supongo que en hardware real tardará menos ;) .

Las tres leyes del software

  1. Ningún programa se arrancará sin ser necesario y sin ser requerido por el usuario.
  2. Ningún programa recopilará, enviará o buscará datos por la jeta, sin ser parte de una tarea solicitada por el usuario.
  3. Todo programa (o componente de software) debe poder desinstalarse sin dejar rastro a la voz de ya.

Propuestas por Supermon, como homólogas a las tres leyes de la robótica, en Halón Disparado.

Y con las que estoy totalmente de acuerdo. Además, se podría añadir la de «Todo programa será software libre y su código liberado», como comentan en Menéame, aunque esa quizás ya sería para nota ;) .

Hal o Win

Hal o Win

Hoy es Halloween; y qué mejor que esta imagen que nada tiene que ver con ello pero que tiene un juego de palabras de los mejores que he visto últimamente. Grande, muy grande. Desde Neoon.

P.D.: Por cierto, la fiesta de Halloween es una fiesta que tiene su origen en la fiesta celta del Samhain y en la fiesta cristiana de Todos los Santos. Por si a alguno se le ocurre decir a estas alturas esto de “yo no celebraré ninguna fiesta traída de EE.UU.”. Por favor, señores, que cada uno celebre lo que quiera ;) .

Implementación básica de soporte de hilos (threads) usando programación orientada a objetos con C++

Después de este título tan largo viene una pregunta: ¿conocéis alguna implementación de hilos (threads) orientada a objetos en C++ que sea medianamente decente?

Si la respuesta es sí, genial, decídmelo en los comentarios para echarle un vistazo.

Si la respuesta es no, no hagáis como yo y os dediquéis a reinventar la rueda y usad la implementación de hilos de la librería Boost. En serio. Es más, usad Boost para todo lo que podáis porque tiene infinidad de cosas muy buenas y muy útiles ya probadas. De hecho, varias cosas de esta librería fueron incluidas en el último estándar de C++, C++11 (antes conocido como C++0x).

Pero, en caso de que os guste investigar y queráis implementar vuestra propia librería de gestión de hilos mediante programación orientada a objetos en C++, aquí tenéis un ejemplo de lo que a mi se me ha ocurrido (y que, de momento, funciona, claro ;) ). Y es un gran ladrillo, avisados estáis.

Lo primero que hay que saber

Lo primero que —de forma resumida— hay que saber es que la implementación de los hilos depende de cada sistema, aunque hay una especificación POSIX llamada pthreads que tienen implementada la mayoría de los sistemas operativos.

Esta implementación generalmente está hecha en C mediante programación estructurada, con lo que tenemos un conjunto de funciones a la que se le pasan parámetros para controlar todo el funcionamiento de los hilos.

Algunas de estas funciones son la de crear e iniciar el hilo —cuyo parámetro principal es un puntero a la función que dicho hilo tiene que ejecutar—, parar el hilo, esperar a que termine dicho hilo, etc.

De lo que trata esta entrada es de encapsular esta funcionalidad en clases para controlar los hilos mediante programación orientada a objetos y así tener un API más consistente, más abstracta y, sobre todo, más moderna. Y aquí es donde los defensores a ultranza de la programación estructurada empiezan a gritar…

Bueno, yo creo que cada cosa está para lo que está y la programación estructurada está muy bien para programar kernels, pero cuando se trata de programar aplicaciones de usuario por parte de un desarrollador de este tipo de aplicaciones (no de sistemas) siempre es mejor facilitarle la tarea a dicho desarrollador. Y con la POO se consigue.

La idea principal

Como comenté arriba, la idea principal es encapsular las funciones de C de gestión de hilos en clases y métodos de C++. Simple y sencillo ¿no? Para ello, diseñaremos las clases necesarias para incluirlas. En principio sólo una, la clase Hilo; o, mejor, la clase Thread, porque me gusta más programar en inglés, por eso de que si compartes tu código llegarás a más gente en este idioma, nos guste o no.

Pero, antes de nada, hay que solucionar un problema importante: a la función de creación del hilo hay que pasarle como parámetro un puntero a la función a ejecutar, pero las clases en C++ no tienen funciones sino que tienen métodos. ¿Y cuál es la diferencia? La principal diferencia es que en el cuerpo de las funciones se tienen los parámetros que se declaran en su definición mientras que en los métodos, además de los parámetros definidos, se tiene un parámetro oculto llamado, generalmente, this (en C++, self en Delphi…) que es un puntero a la instancia del objeto que llama a dicho método.

Y, obviamente, no se puede pasar un puntero a un método a una función que requiere como parámetro un puntero a una función.

La solución está en pasar un método estático de nuestra clase Thread a esta función pasándole como parámetro (visible, no oculto) el puntero a la instancia de nuestra clase Thread. Luego veremos un ejemplo de implementación.

Después de abordar este problema, lo siguiente es encapsular las funciones de gestión de hilos. Aquí, la verdad, no es más que tener un método en nuestra clase por cada función de gestión de hilos que nos interese gestionar. Simple, la verdad.

Lo que hay que ejecutar

El problema de pasar un puntero a una función en lugar de un método como parámetro de la función de creación del hilo está solucionado. Ahora nos queda qué es lo que hay que ejecutar. Y también es sencillo. Se puede declarar un método en nuestra clase Thread llamado execute() que sea el método estándar que siempre ejecute nuestra clase, aunque una solución más elegante (que no más eficiente, sólo estamos hablando de elegancia) sería implementar el operador () (llamada de función) y ser ese el que ejecute nuestro hilo. Repito, cuestión de elegancia, nada más.

Pero, aquí llegamos al segundo problema: ¿qué pasa si quiero ejecutar código diferente sin tener que modificar mi clase Thread cada vez?

La primera solución que se nos ocurre, por obvia, es la herencia. Declaramos la función execute() (o el operador ()) como virtual y hacemos clases derivadas donde lo implementamos. Sin duda, una muy buena solución.

Pero, ¿y si, por cualquier razón, queremos o necesitamos que nuestro hilo pueda ejecutar cualquier método de cualquier otra clase —siempre y cuando cumpla con una definición de parámetros común—?

Aquí es donde nuestra primera planificación de una sola clase Thread puede no ser suficiente.

La implementación

La solución que se me ocurrió pasa por tener dos clases. La primera, una clase Thread como la del texto anterior donde estén declarados e implementados todos los métodos que vamos a usar para gestionar el hilo, teniendo declarado el método execute() como virtual para que se puedan heredar más clases que implementen dicho método. A esta clase la llamaremos AbstractThread (por no permitir tener instancias de la misma y obligar a que sea derivada).

La otra clase sería una clase que hereda de AbstractThread a la que, ahora sí, llamaremos Thread, pero que tiene la peculiaridad de que va a usar plantillas de C++, también conocidas como templates, para indicar qué método de qué clase se va a ejecutar en el hilo.

Al final, contaremos con dos clases: AbstractThread, que es derivable y que es el método execute() el que ejecutará el hilo, y Thread, que está basada en plantillas y será a través de ellas y de su constructor desde donde indicaremos el método a ejecutar y la clase a la que pertenece.

El pseudocódigo de la interfaz de las clases

En este pequeño snippet de código muestro la interfaz (que no la implementación) básica de las dos clases antes mencionadas. En un principio la clase AbstractThread de la que heredan las demás implementando el método execute() y luego la clase Thread usando plantillas.

Clase AbstractThread


typedef int thread_id;

class AbstractThread {
  private:

    /**
     * Función estática que es la que se le pasa a la función
     * 'pthread_create(...)' para que ejecute. El parámetro
     * 'void* arg' será la instancia de la clase cuya función
     * 'execute()' hay que ejecutar. Un ejemplo de implementación
     * está en el siguiente código.
     */
    static void* thread_entry(void* arg) {
      // Código de ejemplo:
      AbstractThread* thread = reinterpret_cast<AbstractThread*>(arg);
      thread->execute();
      return NULL;
    }

  protected:
    /**
     * Función virtual pura que tienen que implementar las clases
     * derivadas y será la que ejecute el hilo.
     */
    virtual void execute() = 0;

  public:
    /**
     * Constructor de la clase.
     */
    AbstractThread();

    /**
     * Destructor de la clase.
     */
    virtual ~AbstractThread();

    /**
     * Devuelve el identificador del hilo (el 'handle').
     */
    thread_id getId();

    /**
     * Inicia el hilo.
     */
    virtual bool start();

    /**
     * Para el hilo.
     */
    virtual bool stop();

    /**
     * Indica si el hilo se está ejecutando o no.
     */
    bool isRunning();

    /**
     * Pide al hilo que pare de ejecutarse. Este es un método
     * de parada conde la implementación de 'execute()' para
     * de forma cooperativa (sin forzar la parada).
     */
    virtual bool requestStop();

    /**
     * Bloquea el hilo que llama a este método hasta que el
     * hilo en cuestión finaliza (espera la finalización del
     * hilo).
     */
    virtual bool wait();

    /**
     * Similar a 'wait()' pero con un tiempo de espera. Si el
     * hilo no finaliza el dicho tiempo de espera, devuelve
     * un error.
     */
    virtual bool timedWait(int microseconds);

    /**
     * Finaliza el hilo de forma abrupta.
     */
    virtual bool kill(int signum);

    /**
     * Devuelve la prioridad del hilo.
     */
    int  priority();

    /**
     * Fija la prioridad del hilo.
     */
    void  priority(int prio);

    /**
     * Función estática que devuelve el identificador
     * del hilo que llama a esta función.
     */
    static thread_id getCurrentThreadId();

    /**
     * Función estática que hace que el hilo que llama a
     * esta función deje de ejecutarse hasta que vuelva
     * a ser planificado por el kernel.
     */
    static void yield();
};

Clase Thread usando plantillas


template <class Worker>
class Thread : public AbstractThread {
  public:
    /**
     * Tipo de dato nuevo que representa la definición del método
     * a ejecutar en este hilo.
     */
    typedef void (Worker::*WorkerMethod)(const Thread<Worker>* thread);

  private:

    /**
     * Puntero a la instancia de la clase cuyo método hay que
     * ejecutar. En caso de que no se pase ninguna instancia en
     * el constructor, se creará (y destruirá) internamente.
     */
    Worker*      fWorker;

    /**
     * Puntero al método a ejecutar dentro de la clase Worker.
     */
    WorkerMethod fMethod;

  protected:

    /**
     * Método heredado de la clase AbstractThread que se implementa
     * para ejecutar el método del Worker en el hilo.
     */
    virtual void execute() {
      if(fWorker != NULL && fMethod != NULL) {
        (fWorker->*fMethod)(this);
      }
    }
  public:

    /**
     * Constructor de la clase donde se le pasa como parámetro
     * un puntero al método a ejecutar. Como no se pasa instancia
     * de la clase a la que pertenece dicho método, se crea una
     * internamente (y se destruye al final).
     */
    Thread(WorkerMethod method = &Worker::execute);

    /**
     * Constructor de la clase donde se le pasa una referencia a
     * una instancia de la clase de donde hay que ejecutar el método
     * y un puntero al método a ejecutar.
     */
    Thread(Worker& worker, WorkerMethod method = &Worker::execute);

    /**
     * Constructor de la clase donde se le pasa un puntero a
     * una instancia de la clase de donde hay que ejecutar el método
     * y un puntero al método a ejecutar.
     */
    Thread(Worker* worker, WorkerMethod method = &Worker::execute);
};

El resto de métodos de la clase Thread serán los mismos que en la clase AbstractThread por lo que no hace falta volver a implementarlos.

Un ejemplo de uso

Un ejemplo del uso de la clase AbstractThread sería la propia clase Thread, donde basta con implementar el método virtual puro execute() para que sea ese código el que se ejecute en el hilo.

Un ejemplo de uso de la clase Thread podría ser:


#include <iostream>

using namespace std;

class Thread;

class MiClase {
  public:
    void ejecutar(const Thread<MiClase>* thread) {
      // Cualquier código a ejecutar en el hilo, por ejemplo:
      int counter = 5;
      while(counter-- > 0) {
        cout << "Hola mundo." << endl;
        sleep(1);
      }
    }
};

MiClase mi_clase* = new MiClase();
Thread<MiClase>* thr = new Thread<MiClase>(mi_clase,&MiClase::ejecutar);

thr->start();
thr->wait();

delete thr;
delete mi_clase;

Algunas notas acerca del código

En los métodos de las clases AbastractThread y Thread se usan valores de retorno para saber si se han ejecutado correctamente o no. Esta decisión está de mano del desarrollador pudiendo usar valores booleanos, números enteros representando códigos de error (usando, por ejemplo, un enum) o excepciones.

Personalmente, aunque la primera implementación que hice fue devolviendo un tipo de dato enumerado representando códigos de error, creo que la mejor forma de hacerlo es usar excepciones debido a que tienen sus ventajas respecto al uso de valores de retorno.

Clases adicionales necesarias

El uso de hilos en las aplicaciones, además de tener que gestionar correctamente la creación de los mismos, implica, al menos, tres cosas más:

  1. Evitar el acceso a la misma zona de memoria de forma simultánea por dos o más hilos.
  2. Sincronizar el orden de acceso de dos o más hilos a un recurso compartido.
  3. Comunicación entre diferentes hilos.

Es por esto que es necesario implementar otras clases (por seguir con el modelo de programación orientada a objetos) con el fin de tener dichos mecanismos.

Por ejemplo, para envitar el acceso a la misma memoria al mismo tiempo habría que implementar una clase Mutex (mutex viene de mutual exclusion) que evita, precisamente, este supuesto, bloqueando el acceso a ciertas zonas de memoria a todos los hilos en caso de que uno de ellos ya esté en dicha zona.

Además, para la sincronización se debería implementar una clase Semaphore que sirve para indicar el orden en que los hilos acceden a los recursos. Además, también podría servir para pasar información (básica) entre hilos.

Y, finalmente, habría que implementar las clases de comunicación teniendo en cuenta los diferentes métodos que se pueden usar como, por ejemplo, la memoria compartida, colas de mensajes, señales, sockets, colas FIFO

Conclusión

Si estáis implementando aplicaciones en C++ que hagan uso de hilos, como dije más arriba, no os compliquéis la vida y usad la librería Boost. Bien implementada, con una buena interfaz y muy, muy probada.

Pero si, por cualquier motivo, tenéis que implementar vuestra propia librería, bien sea porque queréis aprender o porque no queréis que vuestro programa dependa de otras librerías o porque vuestro jefe os lo ha dicho, podéis probar, si queréis, esta idea que he expuesto aquí y, como no, podéis ponerlo en los comentarios… por compartir más que nada ;) .

Y tened en cuenta que esto es muy mejorable, pero la implementación que he hecho funciona bastante bien y es la que estoy usando actualmente. Espero que os sirva ;) .

Tipografía ‘Ubuntu Monospace’

¿Os acordáis de la tipografía Inconsolata que había puesto por aquí como buena fuente de ancho fijo para los editores de desarrollo? Pues he descubierto otra que, si no es tan buena, le está muy cerca. Aunque, bueno, quizás sea más por gustos que por “más buena” o “menos buena”.

Esta es la tipografía ‘Ubuntu Monospace‘, de la familia de tipografías de Ubuntu (que se pueden descargar gratuitamente y que valen para cualquier sistema operativo que soporte fuentes TrueType). Y aquí tenemos dos muestras:

Muestra de la tipografía Ubuntu Monospace en el terminal de Gnome Muestra de la tipografía Ubuntu Monospace en Eclipse

Y estas muestras se pueden comparar con la del terminal con Inconsolata y con la de Eclipse con Inconsolata.

Y a vosotros ¿cuál os gusta más? ¿Cuál usáis para desarrollar?

Mejorando el Explorador de Windows (o no) y otros menesteres

Los chicos de Microsoft se lo curran y han hecho un estudio donde se muestra el uso que los usuarios le damos al Explorador de Windows, esto es, cómo accedemos a las acciones para el tratamiento de archivos —mediante menús, iconos, teclas de acceso rápido, etc.— con el fin de “optimizar” estos componentes para mejorar la experiencia de usuario.

Conforme a este estudio, el nuevo rediseño del Explorador… bueno, deja un poco que desear, teniendo en cuenta que han creado una barra de herramientas mucho más grande e intrusiva que las anteriores con acciones que los usuarios apenas usan (menos del 1 % de las veces se usa la opción de la barra de herramientas en detrimento de otras opciones como el menú contextual o una tecla de acceso rápido).

Pero eso no es lo que quiero destacar aquí. Lo que quiero destacar es este gráfico:

Punto de entrada de los comandos
Punto de entrada de los comandos en el Explorador de Windows

Es decir, según este gráfico, la mayoría de las acciones que un usuario hace en el Explorador de Windows —esto es, tratamiento de archivos— es mediante el menú contextual; luego mediante una combinación de teclas (teclas rápidas); lo siguiente es el uso de la barra de herramientas; y, finalmente, mediante el menú normal de toda la vida.

De este gráfico mi conclusión es que la barra de menús es un punto de entrada de comandos superfluo que, en cuanto estás acostumbrado a usar el software, obvias en favor de los menús contextuales y de las teclas de acceso rápido. Es decir, a mi parecer, la barra de menús sobraría en muchas interfaces de software.

Entonces, me pregunto ¿por qué los chicos de Apple se empeñan en mantener un menú global que ocupa una parte muy importante del escritorio y que, según el estudio de Microsoft, apenas se usaría?

Por poner un sólo ejemplo, esa zona de la pantalla se usa en Google Chrome y en Mozilla Firefox para acceder más rápidamente a las pestañas teniendo en cuenta que el ratón “no se sale” del borde de la pantalla.

Y esto ocurre con todas las interfaces que copian descaradamente la interfaz de Mac OS X (léase Gnome Shell, Unity…). Vamos, para mí es perder espacio y, sobre todo, funcionalidad y usabilidad. Pero no tengáis miedo de que en Mac OS X (y demás) lo quiten alguna vez (aunque sea por configuración). Ante todo hay que tener señas de identidad. Aunque no sean usables.

P.D.: Tengo en mente escribir algún día sobre las disposiciones de los elementos de los escritorio informáticos —esquinas, bordes…— y la tendencia de hoy en día. El problema es la falta de tiempo… y ganas ;) .

Llamadas encadenadas en C++

Esta entrada llevaba tiempo en el tintero, por eso de tener algo de tiempo libre en vacaciones para dedicarlo a leer y elucubrar soluciones.

Por ejemplo, aquí tenemos una prueba de concepto para tener la misma funcionalidad que el operador << de C++ pero usando llamadas encadenas. Y hace (casi) lo mismo: puede enviar a la salida estándar cualquier tipo de variable, incluyendo cadenas, números enteros, números reales y booleanos, sólo que con un formato de llamada diferente.

La implementación de la clase base, pudiendo extenderla lo que se quiera, es la siguiente:

#include <iostream>
#include <string>
#include <sstream>
 

using std::cout;
using std::endl;
using std::flush;
using std::string;
using std::stringstream;
 

class Stdout {
private:
stringstream _buffer;
public:
Stdout() : _buffer() {}
~Stdout() {}
void clear() {
_buffer.str("");
}
Stdout& operator () (const char c) {
string s;
s += c;
_buffer << s;
return *this;
}
Stdout& operator () (const char* text) {
_buffer << text;
return *this;
}
Stdout& operator () (const string text) {
_buffer << text;
return *this;
}
Stdout& operator () (const int i) {
_buffer << i;
return *this;
}
Stdout& operator () (const float f) {
_buffer << f;
return *this;
}
Stdout& operator () (const double d) {
_buffer << d;
return *this;
}
Stdout& operator () (const bool b) {
_buffer << (b ? "true" : "false");
return *this;
}
void flush() {
cout << _buffer.str() << ::flush;
clear();
}
void endl() {
cout << _buffer.str() << ::endl;
clear();
}
};

Y un ejemplo de funcionamiento sería:

int
main() {
Stdout stdout;
stdout("Llamadas encadenadas: enteros: ")(42)(';')(" reales: ")(25.4)(';')(" y booleanos: ")(false)('.').endl();
return 0;
}

Siendo la salida:

$ make && ./test
Llamadas encadenadas: enteros: 42; reales: 25.4; y booleanos: false.
$

No tiene la misma funcionalidad que el operador << porque, entre otras cosas, si se quiere añadir otro elemento imprimible habría que derivar la clase, mientras que con el operador << bastaría con sobrecargarlo. Pero no me digáis que no es elegante ¿eh? :D .

Haiku OS Release 1 Alpha 3

Portada del disco de HaikuOS Release 1 Alpha 3

Todo este tinglado de letras y números en el título de la entrada no es más que para hacerse eco de la salida de la versión Release 1 Alpha 3 del sistema operativo Haiku, el clon libre de mi querido BeOS, un año y un mes después de la Alpha 2 (que no comenté aquí, por cierto).

Los cambios principales son:

  • Hardware: mejor soporte de hardware con nuevos controladores.
  • Sistemas de archivos: soporte para btrfs, ext4 y exFAT.
  • Localización: soporte para traducir y localizar el sistema operativo.
  • Layout API: nuevo API para la colocación de componentes en el desarrollo de aplicaciones.
  • Aplicaciones: mejores en la funcionalidad y en las aplicaciones.
  • Gestión de ventanas: apilado y ordenación de ventanas. Y con accesos directos de teclado.
  • Multimedia: más formatos de audio y vídeo y mejoras en la reproducción de contenidos.
  • Solución de errores: más de 950 incidencias resueltas desde la versión anterior.

En definitiva, el avance es lento de constante, lo que me lleva a pensar que, quizás, dentro de ¿un año? ¿dos años? podamos disfrutar de la primera versión de HaikuOS.

Por supuesto, hoy en día no es alternativa de uso; es, simplemente, para trastear, para aprender, para desarrollar… en definitiva, para quién le gusten las cosas bien hechas. Y sí, he dicho bien hechas. Hay mucha documentación sobre BeOS y Haiku para ver el porqué ;) .

Miniaplicación de indicadores en Ubuntu

Los chicos de Ubuntu han desarrollado en las últimas versiones de este sistema operativo un nuevo concepto de bandeja del sistema (esa zona de la barra de menús donde se concentran iconos de aplicaciones en segundo plano) a lo que ellos llaman miniaplicación de indicadores (indicators applet en inglés); y es algo como esto (los iconos grises de la izquierda):

Indicadores

Esta nuevo sistema pretende imitar la manida bandeja del sistema de Windows donde aplicaciones en segundo plano incrustan su icono para tener constancia de su ejecución y poder realizar acciones sobre las mismas.

Y, a lo que voy: los iconos son muy bonitos, pero la funcionalidad es una basura. Siento ser tan duro, pero me fastidia mucho que involucionemos en lugar de evolucionar. Porque en cada icono de la antigua bandeja del sistema yo podía hacer clic, doble clic y clic con el botón secundario para realizar diferentes acciones, mientras que con este sistema sólo puedo hacer un clic normal y siempre saldrá un menú de acciones.

¿Es esto evolución? ¿Mejora esto la usabilidad del sistema?

A mi entender no. Mismamente, con la aplicación Dropbox (el primer icono empezando por la izquierda de la imagen), antes al hacer clic se abría la carpeta de Dropbox y, con el botón secundario, salía un menú. Ahora siempre sale un menú con el clic. ¿Dónde está el resto de funcionalidad?

¿Creen los desarrolladores de Ubuntu que eliminando funcionalidad se mejora la usabilidad? Yo creo que no.

Hasta aquí mi pataleta de hoy. Otro día seguiré con Unity, que para él tiene…

Manifiesto por la artesanía del software

Como aspirantes a Artesanos del Software estamos subiendo el listón del desarrollo profesional de software mediante su práctica, y el compromiso de ayudar a los demás a aprender el arte. A través de este proceso hemos aprendido a valorar:

No sólo el software que funciona,
sino también el software bien hecho

No sólo responder al cambio,
sino también añadir valor de forma continuada

No sólo los individuos y las interacciones,
sino también una comunidad de profesionales

No sólo la colaboración con el cliente,
sino también las asociaciones productivas

Esto es, en la búsqueda de los elementos de la izquierda hemos encontrado que los elementos de la derecha son indispensables.

Me uno al Manifiesto por la artesanía del software porque yo si considero que hacer software es hacer arte.

Gracias a la traducción desde Mundo Geek.

Actualizar Ubuntu LTS a la siguiente versión en modo consola

Cuando tenemos un servidor corriendo Ubuntu y queremos actualizarlo a la siguiente versión, hay que utilizar el comando:

user@server:~:$ sudo do-release-upgrade

Este comando está en el paquete update-manager-core fácilmente instalable mediante el comando apt-get.

Pero ¿qué pasa cuando estamos en una versión LTS de Ubuntu? Por defecto no nos actualizará ya que sólo actualizará entre versiones LTS. Simplemente dirá “No new release found”.

Para solucionarlo hay que editar el archivo /etc/update-manager/release-upgrades y cambiar la línea que pone Prompt=lts por una que ponga Prompt=normal. Con esto indicaremos al actualizador que busque y actualice a nuevas versiones aunque la versión actual sea LTS y la siguiente no lo sea.

Esta funcionalidad se puede “simular” usando el parámetro -d del comando do-release-upgrade indicando que se quiere actualizar a versiones de desarrollo, con lo que el actualizador tendrá en cuenta todas las versiones, independientemente de si son LTS o no.

Como última nota por seguridad, hay que tener en cuenta que una actualización de este tipo es recomendable hacerla con un terminal in situ ya que, en caso de hacerla a través de ssh, en caso de fallo es muy difícil recuperar el sistema (vamos, eso dice la ayuda, que yo no he llegado a probarlo).

Y es ahora cuando me pongo a hacerlo… ;)