2011-11-26 9 views
6

Necesito escribir una clase que cargue bibliotecas compartidas. La secuencia dlopen()/dlerror() necesita un bloqueo para ser seguro para hilos.Escribir una clase de C++ que se puede usar como estática, pero necesita un bloqueo

class LibLoader { 
    public: 
    LibLoader(string whichLib); 
    bool Load() { Wait(lock); ... dlopen() ... dlerror() ... } 
    bool Unload() { Wait(lock); ... dlclose() ... dlerror() ... } 
    bool IsLoaded() {...} 
    // ... access to symbols... 
    private: 
    static Lock lock; 
} 
Lock Lock::lock; 

Los usuarios de esta clase (no serán múltiples al mismo tiempo) va a querer que sea un miembro estático de esta clase para evitar cargar una biblioteca compartida tiempo múltiple para todos los objetos de la clase:

class NeedsALib { 
public: 
NeedsALib() { if (!myLib.IsLoaded()) { myLib.Load(); } } 
private: 
static LibLoader myLib; 
} 
LibLoader::myLib; 

El problema con este código es que puede/se bloqueará, ya que depende del orden de la estática que se destruye cuando finaliza el programa. Si el bloqueo se va antes de que myLib se bloquee ...

¿Cómo se puede escribir de forma segura que es seguro para la ejecución de subprocesos y no depende del orden de destrucción estática?

+0

... y tu pregunta es? –

+0

@Jeremy: No me gusta que se cuelgue. ;-) ¿Cómo se puede hacer esto mejor? –

+0

¿Tiene objetos estáticos que necesitan cargar bibliotecas compartidas? –

Respuesta

3

Desafortunadamente, creo que la única forma de evitar esto es utilizar directivas de inicialización no importables y únicas, y evitar destruir el bloqueo. Hay dos problemas básicos con los que debe lidiar:

  1. ¿Qué sucede si dos hilos corren para acceder al candado por primera vez? [es decir, no se puede crear el bloqueo de forma diferida]
  2. ¿Qué sucede si el bloqueo se destruye demasiado pronto? [es decir, no se puede crear estáticamente el bloqueo]

La combinación de estas restricciones lo obliga a utilizar un mecanismo no portátil para crear el bloqueo.

En pthreads, la forma más sencilla de manejar esto es con el PTHREAD_MUTEX_INITIALIZER, que le permite inicializar estáticamente las cerraduras:

class LibLoader{ 
    static pthread_mutex_t mutex; 
// ... 
}; 

// never destroyed 
pthread_mutex_t LibLoader::mutex = PTHREAD_MUTEX_INITIALIZER; 

en Windows, puede utilizar synchronous one-time initialization.

alternativa, si puedo garantizar que sólo habrá un hilo antes de que acabe principal, se puede utilizar el patrón Singleton sin destrucción, y acaba de forzar la cerradura para ser tocado antes de main():

class LibLoader { 
    class init_helper { 
    init_helper() { LibLoader::getLock(); } 
    }; 

    static init_helper _ih; 
    static Lock *_theLock; 

    static Lock *getLock() { 
    if (!_theLock) 
     _theLock = new Lock(); 
    return _theLock; 
    } 
    // ... 
}; 

static init_helper LibLoader::_ih; 
static Lock *LibLoader::_theLock; 

Tenga en cuenta que esto hace la suposición posiblemente no portátil (pero muy probable que sea cierta) de que los objetos estáticos de tipo POD no se destruyan hasta que se hayan destruido todos los objetos estáticos no POD. No conozco ninguna plataforma en la que este no sea el caso.

+0

En su primer ejemplo: ¿Por qué LibLoader :: mutex no se destruiría como cualquier otro miembro estático? –

+1

@GeneVincent, pthread_mutex_t es un tipo POD y no se destruye hasta que todos los destructores hayan terminado de ejecutarse y el programa finalice (esto no se aplica necesariamente a Linux, aunque es muy probable que se aplique en todos los sistemas pthreads) – bdonlan

+2

IIRC, 'pthread_mutex_t' es un tipo POD (ya que es literalmente una estructura C), y por lo tanto no tiene constructor o destructor, se inicializa antes de que tenga lugar cualquier inicialización estática. No tengo el estándar, o lo citaría, pero puedo decir que cada arquitectura que he visto (ARM v9, ARM v7, ARM v7m, x86, x86_64, ppc, 68k, 68xx, MSP430 y AVR) , lo hace de esta manera, específicamente: 1) configuración del proceso, 2) inicialización POD (la sección '.data' se inicializa a partir de la imagen del programa y la sección' .bss' se pone a cero), 3) se llaman inicializadores estáticos y finalmente 4) se llama 'main'. –

1

Cumplimentación de los requisitos: se necesitan varias instancias de LibLoader, cada una para una biblioteca diferente, pero debe existir un solo bloqueo para garantizar que no sobrescriban los códigos de error de los demás.

Una forma sería confiar en la inicialización estática y el orden de destrucción dentro de un archivo.

Mejor manera sería no hacer LibLoader campo estático en NeedsALib (y similares). Parece que a estas clases de clientes se les puede pasar una instancia del LibLoader de la derecha en el constructor.

Si la creación de instancias LibLoader fuera de sus clases de cliente no es conveniente, puede hacer que todos los campos estáticos (el candado y los cargadores) punteros y usar el patrón de singleton con inicialización lenta. Luego, al crear el primer cargador, también termina creando el bloqueo. Singleton requeriría bloqueo aquí, pero quizás podría ejecutarlo antes de generar sus hilos. La destrucción también sería explícita y estaría bajo tu control. También puede hacer esto con cargadores solamente (retención de bloqueo estático).

Además, si LibLoader no tiene una gran cantidad de estado para almacenar, puede hacer que cada clase de cliente (NeedsALib etc.) cree su propia instancia LibLoader. Sin embargo, esto es bastante derrochador.

+1

Desafortunadamente, el bloqueo debe ser estático, ya que habrá varios objetos de LibLoader y cuando otro objeto llamará a dlopen() borrará el error para ser capturado con dlerror(). –

+0

Lo que estás tratando de hacer con el bloqueo estático es hacer cumplir que existe una única instancia de bloqueo. Sin embargo, su problema requiere que LibLoader sea un singleton y solo tenga una instancia. En lugar de tener varios cargadores compartiendo un candado, debe tener un solo cargador con un solo candado. –

+0

Necesito múltiples objetos de LibLoader, cada uno para una biblioteca compartida diferente, pero los objetos no pueden cargarse al mismo tiempo para no escribir de nuevo los códigos de error. –

Cuestiones relacionadas