2010-03-21 8 views
26

ero commenting on an answer che lo stoccaggio filo-locale è bello e ha ricordato un altro informative discussion sulle eccezioni in cui avrei dovutoLe eccezioni C++ sono sufficienti per implementare l'archiviazione locale dei thread?

L'unica cosa speciale per l'ambiente di esecuzione all'interno del blocco tiro è che l'oggetto eccezione è riferimento da rethrow.

Mettendo due e due insieme, non si eseguiva un'intera discussione all'interno di un blocco di funzioni della sua funzione principale che lo riempiva di memoria locale del thread?

Sembra funzionare bene, anche se lentamente. Questo romanzo è ben caratterizzato? C'è un altro modo per risolvere il problema? La mia premessa iniziale era corretta? Che tipo di overhead compie get_thread sulla tua piattaforma? Qual è il potenziale per l'ottimizzazione?

#include <iostream> 
#include <pthread.h> 
using namespace std; 

struct thlocal { 
    string name; 
    thlocal(string const &n) : name(n) {} 
}; 

struct thread_exception_base { 
    thlocal &th; 
    thread_exception_base(thlocal &in_th) : th(in_th) {} 
    thread_exception_base(thread_exception_base const &in) : th(in.th) {} 
}; 

thlocal &get_thread() throw() { 
    try { 
     throw; 
    } catch(thread_exception_base &local) { 
     return local.th; 
    } 
} 

void print_thread() { 
    cerr << get_thread().name << endl; 
} 

void *kid(void *local_v) try { 
    thlocal &local = * static_cast< thlocal * >(local_v); 
    throw thread_exception_base(local); 
} catch(thread_exception_base &) { 
    print_thread(); 

    return NULL; 
} 

int main() { 
    thlocal local("main"); 
    try { 
     throw thread_exception_base(local); 
    } catch(thread_exception_base &) { 
     print_thread(); 

     pthread_t th; 
     thlocal kid_local("kid"); 
     pthread_create(&th, NULL, &kid, &kid_local); 
     pthread_join(th, NULL); 

     print_thread(); 
    } 

    return 0; 
} 

Questo richiede la definizione di nuove classi di eccezioni derivate da thread_exception_base, inizializzare la base con get_thread(), ma tutto questo non si sente come un improduttivo insonnia cavalcato Domenica mattina ...

EDIT: Sembra GCC effettua tre chiamate a pthread_getspecific in get_thread. EDIT: e molta introspezione in stack, ambiente e formato eseguibile per trovare il blocco catch che ho perso durante la prima procedura. Questo sembra molto dipendente dalla piattaforma, poiché GCC chiama alcuni libunwind dal sistema operativo. Sovraccarico dell'ordine di 4000 cicli. Suppongo che debba anche attraversare la gerarchia delle classi, ma ciò può essere tenuto sotto controllo.

+0

È una funzione try/catch block consentita per main? –

+1

Certamente, lo standard specifica che il blocco catch principale non gestisce i lanci dai costruttori globali/statici. Non che sia essenziale per questo meccanismo. – Potatoswatter

+0

Cosa succede se viene lanciata una seconda eccezione? –

risposta

9

Nello spirito ludico della questione, che offrono questa creazione incubo orribile:

class tls 
{ 
    void push(void *ptr) 
    { 
     // allocate a string to store the hex ptr 
     // and the hex of its own address 
     char *str = new char[100]; 
     sprintf(str, " |%x|%x", ptr, str); 
     strtok(str, "|"); 
    } 

    template <class Ptr> 
    Ptr *next() 
    { 
     // retrieve the next pointer token 
     return reinterpret_cast<Ptr *>(strtoul(strtok(0, "|"), 0, 16)); 
    } 

    void *pop() 
    { 
     // retrieve (and forget) a previously stored pointer 
     void *ptr = next<void>(); 
     delete[] next<char>(); 
     return ptr; 
    } 

    // private constructor/destructor 
    tls() { push(0); } 
    ~tls() { pop(); } 

public: 
    static tls &singleton() 
    { 
     static tls i; 
     return i; 
    } 

    void *set(void *ptr) 
    { 
     void *old = pop(); 
     push(ptr); 
     return old; 
    } 

    void *get() 
    { 
     // forget and restore on each access 
     void *ptr = pop(); 
     push(ptr); 
     return ptr; 
    } 
}; 

Sfruttando il fatto che secondo lo standard C++, strtok stashes primo parametro in modo che le chiamate successive può passare 0 per recuperare ulteriori token dalla stessa stringa, quindi in un'implementazione thread-aware deve utilizzare TLS.

example *e = new example; 

tls::singleton().set(e); 

example *e2 = reinterpret_cast<example *>(tls::singleton().get()); 

Quindi, fintanto strtok non viene utilizzato nel modo previsto in qualsiasi altra parte del programma, abbiamo un altro slot TLS ricambio.

+0

'strtok' è definito in termini di una sequenza di chiamate, quindi è ambiguo se un'implementazione thread-aware mantiene un puntatore TLS o un globale con un mutex . Sono inorridito, però. – Potatoswatter

+1

Sono contento che abbia avuto l'effetto desiderato! :) Se 'strtok' fosse protetto solo da un mutex che sarebbe stato bloccato per ogni chiamata, ciò non sarebbe di grande aiuto, quindi TLS è l'unica soluzione probabile. Anche se suppongo che potrebbe avere un uso perverso come canale di comunicazione semi-affidabile tra i thread! E per di più potrei costruire un'interfaccia stream, come TCP su IP ... Ora * c'è * una sfida! –

+0

+1 per "creazione da incubo orribile" –

0
void *kid(void *local_v) try { 
    thlocal &local = * static_cast< thlocal * >(local_v); 
    throw local; 
} catch(thlocal &) { 
    print_thread(); 

    return NULL; 
} 

==

void *kid (void *local_v) { print_thread(local_v); } 

mi potrebbe mancare qualcosa qui, ma non è una memoria locale filo, solo inutilmente complicato argomento passaggio. L'argomento è diverso per ogni thread solo perché viene passato a pthread_create, non a causa di alcuna eccezione di giocoleria.


È risultato che in questo esempio mi mancava il fatto che GCC stia producendo chiamate di archiviazione locale con thread effettivo. In realtà rende la questione interessante. Non sono ancora abbastanza sicuro se si tratti di altri compilatori e in che modo è diverso dal chiamare direttamente lo storage di thread.

Continuo a sostenere la mia argomentazione generale secondo cui è possibile accedere agli stessi dati in modo più semplice e diretto, che si tratti di argomenti, stack walking o archiviazione locale dei thread.

+0

L'archiviazione locale dei thread * è * solo un passaggio complicato degli argomenti. 'get_thread' recupera l'argomento da qualsiasi posizione sconosciuta nello stack delle chiamate. 'pthread_create' non è speciale, cf' main'. Il significato qui è che funziona per funzioni senza argomenti, come distruttori o callback mal progettati. – Potatoswatter

+0

Esattamente - ma non funziona per la funzione senza argomenti! Devi passarlo esplicitamente in funzione lanciando prima della chiamata. È solo che la funzione (argomenti) diventa argomento di argomenti ... funzione. – ima

+0

Inoltre, non penso sia del tutto corretto chiamare pthread_getspecific "argument passing", non più dello stack pointer stesso. – ima

0

L'accesso ai dati sullo stack di chiamata della funzione corrente è sempre sicuro. Ecco perché il tuo codice è thread-safe, non a causa dell'uso intelligente delle eccezioni. L'archiviazione locale del thread consente di archiviare i dati per thread e fare riferimento al di fuori dello stack di chiamate immediate.

+0

I dati sullo stack di chiamate non sono sicuri se utilizzati in modo non sicuro. L'archiviazione locale del thread non è intrinsecamente più sicura di qualsiasi altra cosa, è semplicemente composta da thread. – Potatoswatter

+0

Questo non ha nulla a che fare con la sicurezza dei thread, limita solo l'archiviazione locale. – falstro

+0

* Se * hai dichiarato un po 'di dati nello stack e poi lo hai passato a tutte le funzioni come argomento aggiuntivo, avresti effettivamente spazio su disco locale. Il vantaggio di TLS è che non devi aggiungere l'argomento extra. La cosa intelligente di questa idea è che puoi dire "void foo() {throw; } ', dove' throw' non ha un contesto locale per determinare cosa dovrebbe essere lanciato, quindi il runtime deve mantenere uno stack per chiamata, vale a dire uno per thread. Quindi questo è un modo di condividere un valore solo all'interno di una discussione senza usare parametri per passarlo in giro. Proprio come TLS. –

3

Penso che tu abbia qualcosa qui. Questo potrebbe anche essere un modo portabile per ottenere dati in callback che non accettano una variabile di "stato" dell'utente, come hai menzionato, anche a parte l'uso esplicito di thread.

Quindi sembra che tu abbia risposto alla domanda nel tuo argomento: SÌ.

Problemi correlati