Introducción al multiprocesamiento en C++ III



Anteriormente hemos aprendido a levantar un thread y crear un proxy de datos alrededor del puntero a la función correcta que pasar a la función de creación en Windows y UNIX. En esta introducción al multiprocesamiento en C++ III vamos a acabar de desarrollar nuestro wrapper alrededor de la librería nativa en C.

Hasta ahora hemos visto que las dos APIs (Windows y UNIX) difieren bastante entre si, y esa es la tónica general cuando queremos utilizar theads en aplicaciones multiplataforma. Aunque existen implementaciones muy buenas (y por supuesto mucho mejores que esta) nunca está de más trastear un poco con las librerías del sistema.

Obtener el ID de un Thread

Para obtener el ID de un thread solo tenemos que ejecutar una sencilla función en ambas plataformas así que un simple bloque de código de compilación condicional puede solucionarnos la papeleta:

inline ThreadID GetID()
{
    #ifdef WIN32
        return GetCurrentThreadId();
    #else
        return pthread_self();
    #endif
}

Esperar hasta que un thread finalice

En ocasiones necesitaremos parar la ejecución y esperar hasta que otro thread finalice. En Windows esto vuelve a ser un poco más complejo que en UNIX, pero no mucho más:

inline void WaitUntilFinish(ThreadID p_thread)
{
    #ifdef WIN32
        // Esperamos a que el thread finalice
        WaitForSingleObject(g_handlemap[p_thread], INFINITE);
        // Cerramos el handle al thread
        CloseHandle(g_handlemap[p_thread]);
        // Eliminamos el handle del mapa
        g_handlemap.erase(p_thread);
    #else
        // Esperamos a que el thread termine haciendo un "join"
        pthread_join(p_thread, NULL);
    #endif
}
En el código para Windows necesitamos cerrar el HANDLE que apunta hacia el thread por que Windows no destruye un objeto al que haya un HANDLE apuntando y tenemos que cerrarlo de forma explícita.

Matando un thread

Es una mala idea matar un thread por varios motivos, por ejemplo, por que no le damos opción a limpiarse a si mismo pero en ocasiones necesitaremos poder matar uno de ellos por algún motivo. Esto puede producir memory leaks.

inline void Kill(ThreadID &p_thread)
{
    #ifdef WIN32
        // Termina el thread
        TerminateThread(g_handlemap[p_thread], 0);
        // Cierra el handle al thread
        CloseHandle(g_handlemap[p_thread]);
        // Elimina el handle del mapa
        g_handlemap.erase(p_thread);
    #else
        // Cancelamos el thread
        pthread_cancel(p_thread);
    #endif
}
En este último ejemplo pasa lo mismo que en el ejemplo anterior, en Windows necesitamos realizar un par de pasos adicionales.

Echando a dormir un Thread

Echar a dormir los threads es algo muy útil por que no consumirán recursos y el sistema operativo controlará su estado muy bien. En Windows se usa la función Sleep y en UNIX usleep que usa microsegundos como argumento en lugar de milisegundos.

inline void SleepThread(int p_milliseconds = 1)
{
    #ifdef WIN32
        Sleep(p_milliseconds);
    #else
        usleep(p_milliseconds * 1000);
    #endif
}

El código completo

Por motivos de simplicidad vamos a mantener todo en un archivo de cabecera llamado gthreads.h y no vamos a crear una clase Threads (que por otro lado sería lo lógico en un entorno orientado a objetos) y vamos a mantener todo relativamente simple. Podríamos incluir el archivo gthreads.h en cualquier proyecto de C++ y empezar a trabajar con él.

#ifndef _Gthreads_h_

define _Gthreads_h@#@

include <exception>

ifdef WIN32

#include &lt;windows&#46;h&gt;
#include &lt;map&gt;

else

#include &lt;pthreads&#46;h&gt;
#include &lt;unistd&#46;h&gt;

endif

namespace ThreadLib
{ class Exception : public std::exception { public: Exception(Error p_error = Unspecified) { m_error = p_error; } Error GetError() cost { return m_error; } protected: Error m_error; }; typedef void (ThreadFunc)(void); #ifdef WIN32 typedef DWORD ThreadID; extern std::map
<dword , HANDLE> g_handlemap; #else typedef pthread_t ThreadID; #endif class ProxyData { ThreadFunc m_func; void *m_params; }; #ifdef WIN32 DWORD WINAPI ProxyFunc(void *p_data) #else void *ProxyFunc(void *p_data) #endif { ProxyData data = (ProxyData)p_data; data->m_func(data->m_params); delete data; return 0; } inline ThreadID Create(ThreadFunc p_func, void *p_param) { ThreadID th; ProxyData *data = new ProxyData; data->m_func = p_func; data->m_params = p_param; #ifdef WIN32 HANDLE h; h = CreateThread(NULL, 0, ProxyFunc, data, 0, &th); if (h != 0) { g_handlemap[th] = h; } #else pthread_create(&th, 0, ProxyFunc, data); #endif if (th==0) { delete data;
throw Exception(CreationFail); } return th; } inline ThreadID GetID() { #ifdef WIN32 return GetCurrentThreadId(); #else return pthread_self(); #endif } inline void WaitUntilFinish(ThreadID p_thread) { #ifdef WIN32 WaitForSingleObject(g_handlemap[p_thread], INFINITE); CloseHandle(g_handlemap[p_thread]); g_handlemap.erase(p_thread); #else
pthread_join(p_thread, NULL); #endif } inline void SleepThread(int p_milliseconds = 1) { #ifdef WIN32 Sleep(p_milliseconds); #else usleep(p_milliseconds * 1000); #endif }
}

endif // _Gthreads_h_

Como he dicho anteriormente lo suyo sería crear una clase Thread y que el código que queramos se ejecute en un thread a parte heredara de esa clase y tuviera que implementar una interfaz específica. Tampoco hemos hecho un wrapper alrededor de los mutex algo que haremos en el siguiente post de la serie.



En Genbeta Dev | Introducción al multiprocesamiento en C++

Portada de Genbeta