13

Mi è stato recentemente reso noto che l'archiviazione locale dei thread è limitata su alcune piattaforme. Ad esempio, i documenti per la libreria C++ boost :: thread read:Su quali piattaforme è limitata la memoria locale del thread e quanto è disponibile?

"Nota: esiste un limite specifico di implementazione per il numero di oggetti di archiviazione specifici del thread che è possibile creare e questo limite può essere piccolo."

Ho cercato di scoprire i limiti per piattaforme diverse, ma non sono riuscito a trovare una tabella autorevole. Questa è una domanda importante se stai scrivendo un'app crossplatform che utilizza TLS. Linux è stata l'unica piattaforma per la quale ho trovato informazioni, sotto forma di una patch Ingo Monar inviata nel 2002 all'elenco dei kernel con l'aggiunta del supporto TLS, dove ha menzionato: "Il numero di aree TLS è illimitato, e non c'è nessun sovraccarico di allocazione associato al supporto TLS. " Che se fosse ancora vero nel 2009 (vero?) È piuttosto carino.

Ma per quanto riguarda Linux oggi? OS X? Finestre? Solaris? Sistemi operativi integrati? Per i sistemi operativi che funzionano su più architetture varia tra architetture?

Edit: Se siete curiosi perché ci potrebbe essere un limite, si consideri che lo spazio per la memorizzazione locale filo verrà preallocato, quindi dovrete pagare un costo per esso su ogni singolo filo. Anche una piccola quantità di fronte a molte discussioni può essere un problema.

risposta

0

È possibile che la documentazione di boost stia semplicemente parlando di un limite configurabile generale, non necessariamente di un limite rigido della piattaforma. Su Linux, il comando ulimit limita i processi delle risorse (numero di thread, dimensioni dello stack, memoria e un mucchio di altre cose). Questo avrà un impatto indiretto sulla memoria locale del thread. Sul mio sistema, non sembra esserci una voce in ulimit specifica per l'archiviazione locale dei thread. Altre piattaforme possono avere un modo per specificarlo da solo. Inoltre, penso che in molti sistemi multiprocessore, la memoria locale del thread sarà in memoria dedicata a quella CPU, quindi è possibile imbattersi in limiti di memoria fisica molto prima che il sistema nel suo complesso abbia esaurito la memoria. Suppongo che ci sia una sorta di comportamento di riserva per localizzare i dati nella memoria principale in quella situazione, ma non lo so. Come puoi dire, sto congetturando molto. Speriamo che vi porta ancora nella giusta direzione ...

3

Ho solo usato TLS su Windows, e ci sono piccole differenze tra le versioni in quanto può essere utilizzato: http://msdn.microsoft.com/en-us/library/ms686749(VS.85).aspx

presumo che il codice è solo il targeting dei sistemi operativi che supportano i thread: in passato ho lavorato con sistemi operativi embedded e desktop che non supportano il threading, quindi non supportano TLS.

9

Su Linux, se si utilizza __thread dati TLS, l'unico limite è fissato dal vostro spazio di indirizzi disponibili, come questi dati vengono semplicemente ripartito come RAM regolare fa riferimento il gs (su x86) o fs (su x86-64) descrittori di segmento. Si noti che, in alcuni casi, l'allocazione dei dati TLS utilizzati dalle librerie caricate dinamicamente può essere eliminata nei thread che non utilizzano tali dati TLS.

TLS assegnato da pthread_key_create e gli amici, tuttavia, è limitato agli slot PTHREAD_KEYS_MAX (questo vale per tutte le implementazioni di pthreads conformi).

Per ulteriori informazioni sull'implementazione TLS su Linux, vedere ELF Handling For Thread-Local Storage e The Native POSIX Thread Library for Linux.

Detto questo, se è necessaria la portabilità, la soluzione migliore è ridurre al minimo l'utilizzo di TLS: inserire un singolo puntatore in TLS e inserire tutto ciò di cui si ha bisogno in una struttura dati appesa a quel puntatore.

0

L'archiviazione locale del thread declspec su Windows ti limita a usarlo solo per variabili statiche, il che significa che sei sfortunato se vuoi usarlo in modi più creativi.

C'è un'API di basso livello su Windows, ma ha una semantica rotta che rende molto difficile inizializzarsi: non si può dire se la variabile è già stata vista dal thread, quindi è necessario esplicitamente inizializzarlo quando si crea il thread.

D'altra parte, l'API pthread per l'archiviazione locale dei thread è ben congegnata e flessibile.

0

Uso una classe template semplice per fornire l'archiviazione locale dei thread. Questo semplicemente avvolge un std::map e una sezione critica. Questo quindi non soffre di problemi locali relativi a thread specifici della piattaforma, l'unico requisito della piattaforma è ottenere l'id corrente del thread come in numero intero. Potrebbe essere un po 'più lento della memoria locale del thread nativo, ma può memorizzare qualsiasi tipo di dati.

Di seguito è riportata una versione ridotta del mio codice. Ho rimosso la logica del valore predefinito per semplificare il codice. Poiché è possibile memorizzare qualsiasi tipo di dati, gli operatori di incremento e decremento sono disponibili solo se T li supporta. La sezione critica è richiesta solo per proteggere la ricerca e l'inserimento nella mappa. Una volta che viene restituito un riferimento, è sicuro utilizzare unprotected poiché solo il thread corrente utilizzerà questo valore.

template <class T> 
class ThreadLocal 
{ 
public: 
    operator T() 
    { 
     return value(); 
    } 

    T & operator++() 
    { 
     return ++value(); 
    } 

    T operator++(int) 
    { 
     return value()++; 
    } 

    T & operator--() 
    { 
     return --value(); 
    } 

    T operator--(int) 
    { 
     return value()--; 
    } 

    T & operator=(const T& v) 
    { 
     return (value() = v); 
    } 

private: 
    T & value() 
    { 
     LockGuard<CriticalSection> lock(m_cs); 
     return m_threadMap[Thread::getThreadID()]; 
    } 

    CriticalSection  m_cs; 
    std::map<int, T> m_threadMap; 
}; 

Per utilizzare questa classe io in genere dichiaro un membro statico all'interno di una classe ad esempio

class DBConnection { 
    DBConnection() { 
     ++m_connectionCount; 
    } 

    ~DBConnection() { 
     --m_connectionCount; 
    } 

    // ... 
    static ThreadLocal<unsigned int> m_connectionCount; 
}; 

ThreadLocal<unsigned int> DBConnection::m_connectionCount 

potrebbe non essere perfetta per ogni situazione, ma copre il mio bisogno e posso facilmente aggiungere qualsiasi funzionalità Si tratta di manco mentre li scopro.

bdonlan è corretto questo esempio non ripulire dopo l'uscita thread. Tuttavia questo è molto facile da aggiungere manualmente.

template <class T> 
class ThreadLocal 
{ 
public: 
    static void cleanup(ThreadLocal<T> & tl) 
    { 
     LockGuard<CriticalSection> lock(m_cs); 
     tl.m_threadMap.erase(Thread::getThreadID()); 
    } 

    class AutoCleanup { 
    public: 
     AutoCleanup(ThreadLocal<T> & tl) : m_tl(tl) {} 
     ~AutoCleanup() { 
      cleanup(m_tl); 
     } 

    private: 
     ThreadLocal<T> m_tl 
    } 

    // ... 
} 

Poi un filo che conosce fa uso esplicito del ThreadLocal può usare ThreadLocal::AutoCleanup nella sua funzione principale di ripulire la variabile.

Oppure, nel caso di DBConnection

~DBConnection() { 
    if (--m_connectionCount == 0) 
     ThreadLocal<int>::cleanup(m_connectionCount); 
} 

Il metodo cleanup() è statico in modo da non interferire con operator T(). È possibile utilizzare una funzione globale per chiamare ciò che dedurrebbe i parametri Template.

+0

Che non pulisce dopo la morte di un filo ... – bdonlan

+0

Si è corretto, la pulizia corrente dovrebbe essere eseguita manualmente – iain

1

Su Mac, so di Task-Specific Storage nel Multiprocessing Services API:

MPAllocateTaskStorageIndex 
MPDeallocateTaskStorageIndex 
MPGetTaskStorageValue 
MPSetTaskStorageValue 

Questo sembra molto simile alla memoria locale thread di Windows.

Non sono sicuro che questa API sia attualmente consigliata per l'archiviazione locale dei thread sul Mac. Forse c'è qualcosa di più recente.