Thread Local Storage w klasach

0

Obecnie w VC++ (a także w gcc z tego co wiem) można zrobić takie TLSy:

class A
{
...
__declspec(thread) static int localVar;
}
int A::localVar = 0;

Zmienna localVar musi być statyczna, inaczej kompilator tego nie skompiluje. A ja właśnie chciał bym, żeby ta zmienna nie była statyczna, tak żeby localVar była jednocześnie powiązana z instancją klasy oraz z wątkiem. Przykładowo w Javie da się coś takiego bez żadnego problemu zrobić:

class Klazz
{   
    public ThreadLocal<Integer> uniqueNum = new ThreadLocal<Integer>() {
        @Override protected Integer initialValue() {
            return 0;
    }

    };
}
public class Main {
    public static void main(String[] args) {
        Klazz k1 = new Klazz();
        Klazz k2 = new Klazz();
        k1.uniqueNum.set(1);
        System.out.println("Val: " + Integer.toString(k1.uniqueNum.get()));
        System.out.println("Val: " + Integer.toString(k2.uniqueNum.get()));
    }
}

Jedyne co mi w C++ przychodzi do głowy to samemu stworzyć taką klasę o tak:

template<typename T>
class ThreadLocal
{
public:
    T& ref(void* ptr)
    {
        if(localMap == 0)
        {
            localMap = new std::map<void*, T>;
        }
        return (*localMap)[ptr];
    }

    void release()
    {
        delete localMap;
        localMap = 0;
    }

private:
    __declspec(thread) static std::map<void*, T>* localMap;
};
template<typename T> std::map<void*, T>* ThreadLocal<T>::localMap = 0;

class A
{
public:
    void set(){localA.ref(this) = 1;}

    int get(){return localA.ref(this);}

    void release(){localA.release();}
private:
    ThreadLocal<int> localA;
};

    A a1, a2;
    a1.set();
    printf("%d %d", a1.get(), a2.get());

    a1.release();
    a2.release();

Problemy są dwa:

  1. Wydajność - muszę mieć dodatkowy kontener w którym będę wyszukiwał this.
  2. Zwalnianie pamięci - na koniec każdego wątku muszę wywoływać metodę release, która mi zwolni pamięc.
    Pierwszy punkt da się przełknąć, natomiast 2 jest wybitnie irytujący. Na Windowsie o ile mi wiadomo nie ma sposobu na ustawienie callbacka, który powiadomi mnie o tym, że dany thread wychodzi (pomijam wszystkie brudne triki typu hookowanie IAT, notyfikacja z jądra itd). Używając highlevelowego freameworku jak pthreads mogę użyć np. pthreads_cleanup_push do zarejestrowania takiego callbacku, ale ja niekoniecznie chcę używać pthreads.
    Może ktoś zna lepsze rozwiązania tego problemu bo nie chce mi się wierzyć, że nic lepszego nie istnieje...
1

1) Możesz użyć STL map lub Qt QMap taki, że jest indeksowany numerem wątka (który jest niepowtarzalny) i zwraca wartość jakiegoś tam typu (takiego jakiego potrzebujesz),
2) Możesz użyć frameworków C++ np. Qt posiada klasę QThreadStorage.

0

Możesz użyć STL map lub Qt QMap taki, że jest indeksowany numerem wątka (który jest niepowtarzalny) i zwraca wartość jakiegoś tam typu (takiego jakiego potrzebujesz

To akurat nie ma sensu bo do takiej kolekcji trzeba synchronizować dostęp, a cała idea TLS jest właśnie po to, żeby nie robić blokad.

Możesz użyć frameworków C++ np. Qt posiada klasę QThreadStorage

Chętnie przejrze jak to zaimplementowali chociaż idę o zakład, że jest po prostu dodany mechanizm powiadomień kiedy QT wątek wychodzi.
W sumie jeszcze można by sobie załadować DLLke do procesu i odbierać DLL_THREAD_DETACH i tam zwalniać pamięc, ale może jest jakiś lepszy sposób.

0

To akurat nie ma sensu bo do takiej kolekcji trzeba synchronizować dostęp, a cała idea TLS jest właśnie po to, żeby nie robić blokad.

Nie bardzo chwytam. Jeśli masz np. STL map indeksowany przez numer wątka, to gdzie jest potrzeba synchronizacji skoro tylko wątek o numerze x zapisuje pod indeks x? Jeśli jest jeden pisarz i wielu czytelników, to wtedy nie ma przecież potrzeby synchronizacji.

0

Nie bardzo chwytam. Jeśli masz np. STL map indeksowany przez numer wątka, to gdzie jest potrzeba synchronizacji skoro tylko wątek o numerze x zapisuje pod indeks x? Jeśli jest jeden pisarz i wielu czytelników, to wtedy nie ma przecież potrzeby synchronizacji.

Wątki mogą być tworzone i usuwane współbieżnie co oznacza, że dodawania wątku do mapy i odczytywanie mapy też mogą być wykonywane współbieżnie. Przez co dostęp do mapy musi być synchronizowany. Można by ewentualnie użyć lockless hash mapy, żeby uniknąć blokad ale to IMHO overkill.

Jeśli jest jeden pisarz i wielu czytelników, to wtedy nie ma przecież potrzeby synchronizacji

Jest potrzeba synchronizacji, nawet jeżeli jest jeden pisarz i wielu czytelników. Przykładowo std::map to tak na prawdę drzewo czerwono-czarne, które przy dodawaniu może zostać zrebalansowane. Jeżeli czytelnik będzie próbował coś odczytać podczas tej procedury to najprawdopodobniej zaliczy crash.

0

Jest potrzeba synchronizacji, nawet jeżeli jest jeden pisarz i wielu czytelników
Na pewno nie przy jednym intcie..

Przykładowo std::map to tak na prawdę drzewo czerwono-czarne, które przy dodawaniu może zostać zrebalansowane.
Wydaje mi się, że komunikacja między wątkami powinna być jak najprostsza. I jak najmniejsza. Przepychanie dużych obiektów, skomplikowanych klas, szybko się skończy bólem głowy.

1

W TLS chodzi chyba o utworzenie statycznego stanu, ale nie globalnego dla całego procesu, a tylko dla wątku. Nie musi być wcale żadnej komunikacji między wątkami, żeby TLS miało sens.

1

Wątki mogą być tworzone i usuwane współbieżnie co oznacza, że dodawania wątku do mapy i odczytywanie mapy też mogą być wykonywane współbieżnie. Przez co dostęp do mapy musi być synchronizowany. Można by ewentualnie użyć lockless hash mapy, żeby uniknąć blokad ale to IMHO overkill.

Aha, to w takim przypadku możesz zastosować współbieżny wzorzec projektowy Pula. W tym przypadku jest to pula wątków. Są dwa zbiory: zbiór wątków aktywnych i zbiór wątków nieaktywnych. Ten pierwszy zbiór może mieć stały rozmiar (taki, który dobrze pasuje do danego sprzętu). O przenoszeniu wątków między zbiorami decyduje jeden nadzorca (nie ma więc problemu z jego synchronizacją). Ten nadzorca jest też odpowiedzialny za sterowanie rozmiarem i zawartością mapy. Zatem dodawania wątku do mapy (lub jego usuwanie) nie jest wykonywane współbieżnie.

Ponieważ realizacja tego wzorca oznacza przenoszenie sporej części aktywności programu do innych plików, więc jedynym czym się martwisz przy rozwiązywaniu problemu z tematu to tylko jedna mapa. A to oznacza de facto uproszczenie (przez modularyzację) kodu.

0

Pula jest to jakiś pomysł, ale niestety mój kod ma być jak najbardziej ogólny.
Sprawdziłem jak wygląda implementacja w booście thread_specific_ptr. Generalnie tak jak myślałem - jest mechanizm callbacków wołanych gdy wątek wychodzi co implikuje, że thread_specific_ptr może być tylko użyty w przypadku wątków stworzonych przez boosta (a nie np. przez CreateThread winapi).

1
0x200x20 napisał(a):

Na Windowsie o ile mi wiadomo nie ma sposobu na ustawienie callbacka, który powiadomi mnie o tym, że dany thread wychodzi (pomijam wszystkie brudne triki typu hookowanie IAT, notyfikacja z jądra itd)
Może po prostu opakuj sobie wątek własną klasą? To by było chyba najprostsze.

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0