2011-11-26 15 views
6

Potrzebuję napisać klasę, która ładuje biblioteki współdzielone. Sekwencja dlopen()/dlerror() potrzebuje blokady, aby była bezpieczna dla wątków.Zapisywanie klasy C++, która może być używana jako statyczna, ale wymaga blokady

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; 

Użytkownicy tej klasy (nie będzie wielokrotnością w tym samym czasie) będzie chciał, aby to statyczne członkiem tej klasy, aby uniknąć ładowania wspólna Biblioteka czas dla każdego obiektu klasy:

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

Problem z tym kodem polega na tym, że może się zawieść, ponieważ opiera się na kolejności niszczenia statyki po zakończeniu programu. Jeśli blokada zniknie, zanim myLib się zawiesza ...

Jak można to napisać w bezpieczny sposób, który jest bezpieczny dla wątków i nie polega na kolejności statycznego zniszczenia?

+0

... a twoje pytanie brzmi? –

+0

@Jeremy: Nie podoba mi się, że się rozbił. ;-) Jak to zrobić lepiej? –

+0

Czy masz statyczne obiekty, które muszą załadować biblioteki współdzielone? –

Odpowiedz

3

Niestety, wydaje mi się, że jedynym sposobem na uniknięcie tego jest użycie nieprzewidywalnych jednorazowych dyrektyw inicjalizacyjnych i uniknięcie w ogóle zniszczenia zamka. Są dwa podstawowe problemy, z którymi musisz sobie poradzić:

  1. Co się stanie, jeśli dwa wątki będą ścigały się, aby uzyskać dostęp do zamka po raz pierwszy? [tj. nie możesz leniwy - utwórz zamka]
  2. Co się stanie, jeśli zamek zostanie zniszczony zbyt wcześnie? [tj. nie można statycznie utworzyć blokady]

Połączenie tych ograniczeń zmusza do użycia mechanizmu niemobilnego w celu utworzenia zamka.

Na pthreads, najprostszym sposobem radzenia sobie z tym jest, co pozwala statycznie zainicjować blokuje PTHREAD_MUTEX_INITIALIZER:

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

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

Z okien można użyć synchronous one-time initialization.

Alternatywnie, jeśli można Gwarantujemy, że będzie tylko jeden wątek przed głównych tras, można użyć Singleton bez zniszczenia, i po prostu zmusić blokadę być dotykane przed 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; 

Zauważ, że to czyni możliwym nieprzenośne (ale bardzo prawdopodobne, że jest prawdziwe) założenie, że statyczne obiekty typu POD nie zostaną zniszczone, dopóki wszystkie statyczne obiekty nie-POD nie zostaną zniszczone. Nie jestem świadomy żadnej platformy, w której tak nie jest.

+0

W twoim pierwszym przykładzie: Dlaczego LibLoader :: mutex nie byłby destreyed jak każdy inny statyczny członek? –

+1

@GeneVincent, pthread_mutex_t jest typem POD i nie jest niszczony, dopóki wszystkie destruktory nie skończą działać i program się zakończy (niekoniecznie odnosi się to do czegokolwiek innego niż Linux, chociaż jest bardzo prawdopodobne, że będzie stosowany na wszystkich systemach pthreads) – bdonlan

+2

IIRC, ponieważ 'pthread_mutex_t' jest typem POD (ponieważ jest to dosłownie struktura C), a zatem nie ma konstruktora ani destruktora, jest inicjowany przed rozpoczęciem dowolnej statycznej inicjalizacji. Nie mam standardu, albo cytuję, ale mogę powiedzieć, że każda architektura widziałem (ARM v9, ARM v7, ARM v7m, x86, x86_64, ppc, 68k, 68xx, MSP430 i AVR) , robi to w ten sposób, w szczególności: 1) konfiguracja procesu, 2) inicjalizacja POD (sekcja '.data' jest inicjowana z obrazu programu, a sekcja' .bss' jest wyzerowana), 3) wywoływane są statyczne inicjatory i wreszcie 4) Nazywa się 'main'. –

1

Zawijanie wymagań: potrzebnych jest wiele instancji LibLoader, każda dla innej biblioteki, ale musi istnieć pojedyncza blokada, aby zapewnić, że nie będą one nadpisywać kodów błędów drugiej strony.

Jednym ze sposobów jest poleganie na statycznej inicjalizacji i kolejności niszczenia w pliku.

Lepszym sposobem nie byłoby utworzenie statycznego pola LibLoader w NeedsALib (i tym podobne). Wygląda na to, że te klasy klientów mogą zostać przekazane instancji o odpowiedniej architekturze LibLoader w konstruktorze.

Jeśli tworzenie instancji poza swoimi klasami klienckimi nie jest wygodne, można utworzyć wszystkie wskaźniki statyczne (blokadę i ładowarki) i zastosować wzorzec singletowy z leniwą inicjalizacją. Następnie, podczas tworzenia pierwszego programu ładującego, kończy się także tworzenie blokady. Sam Singleton wymagałby zablokowania, ale możesz go uruchomić przed uruchomieniem wątków. Zniszczenie będzie również jawne i pod twoją kontrolą. Możesz to również zrobić tylko z ładowarkami (zachowując statyczną blokadę).

Ponadto, jeśli LibLoader nie ma dużo stanu do zapisania, można wykonać każdą klasę klienta (NeedsALib itd.), Aby utworzyć własną wersję LibLoader. Jest to wprawdzie dość marnotrawstwo.

+1

Niestety blokada musi być statyczna, ponieważ będzie wiele obiektów LibLoader, a gdy inny obiekt wywoła dlopen(), usunie błąd, który zostanie pobrany za pomocą dlerror(). –

+0

To, co próbujesz zrobić za pomocą blokady statycznej, wymusza, że ​​istnieje tylko jedna instancja blokady. Jednak Twój problem wymaga, aby LibLoader był singletonem i miał tylko jedną instancję. Zamiast udostępniania wielu blokad przez wiele ładowarek, powinieneś mieć jeden moduł ładujący z pojedynczą blokadą. –

+0

Potrzebuję wielu obiektów LibLoader, każdy dla innej udostępnionej biblioteki, ale obiekty mogą nie ładować się w tym samym czasie, aby nie eoverwrite kodów błędów. –

Powiązane problemy