2013-02-07 12 views
5

Ho trovato una contraddizione in MSDN per quanto riguarda i valori iniziali per l'archiviazione locale dei thread. This page dice:La memoria locale thread-thread inizializza i valori?

Quando vengono creati i fili, il sistema alloca una matrice di valori LPVOID per TLS, che vengono inizializzati a NULL.

Questo mi porta a credere che se io chiamo TlsGetValue con un indice valido da un thread che non ha mai chiamato TlsSetValue per lo stesso indice, quindi dovrei ottenere un puntatore nullo.

This page, comunque, dice:

Spetta al programmatore per assicurare ... che il thread chiama TlsSetValue prima di chiamare TlsGetValue.

Questo suggerisce che non è possibile fare affidamento sul valore restituito da TlsGetValue a meno che non si sia certi che sia stato inizializzato esplicitamente con TlsSetValue.

Tuttavia la second page simultaneamente rinforza il comportamento inizializzato a zero da anche dicendo:

I dati memorizzati in uno slot TLS possono avere un valore di 0 perché ha ancora il suo valore iniziale o perché il filo chiamato la funzione TlsSetValue con 0.

Così ho due affermazioni dicendo che i dati viene inizializzato a null (o 0), e uno dicendo che devo inizializzare in modo esplicito prima di leggere il valore. Sperimentalmente, i valori sembrano essere inizializzati automaticamente con puntatori nulli, ma non ho modo di sapere se sono solo fortunato e se sarà sempre così.

Sto cercando di evitare l'utilizzo di una DLL solo per allocare su DLL_THREAD_ATTACH. Mi piacerebbe fare allocazione pigra lungo le linee di:

LPVOID pMyData = ::TlsGetValue(g_index); 
if (pMyData == nullptr) { 
    pMyData = /* some allocation and initialization*/; 
    // bail out if allocation or initialization failed 
    ::TlsSetValue(g_index, pMyData); 
} 
DoSomethingWith(pMyData); 

È un modello affidabile e sicuro? O devo inizializzare esplicitamente lo slot in ogni discussione prima che io provi a leggerlo?

UPDATE: La documentazione dice anche che TlsAlloc zeros out the slots for the allocated index. Quindi, se uno slot è stato utilizzato in precedenza da un'altra parte del programma, sembra irrilevante.

risposta

10

La documentazione è troppo utile quando dice che il valore iniziale che il sistema assegna a TLS è zero. L'affermazione è vera ma non utile.

Il motivo è che le applicazioni possono liberare gli slot TLS chiamando lo , quindi quando si assegna uno slot, non vi è alcuna garanzia che si sia la prima persona a ricevere tale slot.Pertanto, non si sa se il valore è lo 0 iniziale assegnato dal sistema o qualche altro valore indesiderato assegnato dal precedente proprietario dello slot.

considerare:

  • Componente A chiama TlsAlloc e viene assegnato slot 1. Slot 1 non è mai stato utilizzato, in modo che contenga il suo valore iniziale pari a 0.
  • Componente A chiama TlsSetValue(1, someValue).
  • Il componente A chiama TlsGetValue(1) e riceve someValue indietro.
  • Il componente A è terminato e chiama TlsFree(1).
  • Componente B chiama TlsAlloc e viene assegnato slot 1.
  • Componente B chiama TlsGetValue(1) e ottiene someValue indietro perché questo è il valore spazzatura lasciata da componente A.

Pertanto, è il programmatore assicurarsi che il thread chiami TlsSetValue prima di chiamare TlsGetValue. In caso contrario, il tuo TlsGetValue leggerà spazzatura rimanente.

La documentazione fuorviante sta dicendo "Il valore di default di immondizia rimanente è pari a zero", ma questo non utile perché non avete idea di quello che è successo alla fessura tra il momento il sistema inizializzato esso e alla fine ottenuto dato a voi.

follow-up di Adrian mi ha spinto a studiare nuovamente la situazione, e in effetti gli zeri del kernel fuori slot quando Componente A chiama TlsFree(1), in modo che quando il componente B chiama TlsGetValue(1) diventa pari a zero. Ciò presuppone che non ci sia un bug nel componente A in cui chiama TlsSetValue(1) dopo TlsFree(1).

ragionamento
+0

Bel ragionamento, ma si scopre che TlsAlloc re-inizializzerà gli slot a zero, quindi il componente B tornerà a 0 anziché il valore spazzatura lasciato dal componente A. Ho aggiunto una risposta con maggiori dettagli. –

+0

@Adrian Reinizializza lo slot per il thread corrente. Gli altri fili sono ancora fottuti, credo. [AGGIORNAMENTO: gli altri thread non sono effettivamente avvitati. Sono azzerati al tempo TlsFree.] –

+0

Grazie per il look in più. Ho anche giocato abbastanza per vedere che sono reinizializzati su TlsFree, ma non avevo ancora controllato gli altri thread. –

1

Non vedo la contraddizione. La seconda pagina ti dice semplicemente che il sistema inizializza tutti gli slot TLS a 0 ma che al di là di alcuni controlli di limiti rudimentali, non ha modo di sapere se un particolare indice TLS contiene dati validi (0 potrebbe essere un set-by-the-user perfettamente valido valore anche!) e che spetta a te assicurarti che l'indice che richiedi sia l'indice che vuoi e che contenga dati validi.

+0

"Spetta al programmatore assicurarsi che il thread richiami TlsSetValue prima di chiamare TlsGetValue," è la parte che suona come una contraddizione. –

2

Raymond Chen aveva perfettamente senso, così ho accettato la sua risposta di ieri, ma oggi ho notato un'altra linea chiave nella documentazione MSDN per TlsAlloc:

Se la funzione riesce, il valore restituito è un indice TLS . Gli slot per l'indice vengono inizializzati a zero.

Se TlsAlloc davvero inizializzare le slot a zero, allora non devi preoccuparti di un valore di spazzatura lasciata da un precedente utente di tale slot. Per verificare che TlsAlloc realtà non si comporta in questo modo, ho eseguito il seguente esperimento:

void TlsExperiment() { 
    DWORD index1 = ::TlsAlloc(); 
    assert(index1 != TLS_OUT_OF_INDEXES); 
    LPVOID value1 = ::TlsGetValue(index1); 
    assert(value1 == 0); // Nobody else has used this slot yet. 
    value1 = reinterpret_cast<LPVOID>(0x1234ABCD); 
    ::TlsSetValue(index1, value1); 
    assert(value1 == ::TlsGetValue(index1)); 
    ::TlsFree(index1); 

    DWORD index2 = ::TlsAlloc(); 
    // There's nothing that requires TlsAlloc to give us back the recently freed slot, 
    // but it just so happens that it does, which is convenient for our experiment. 
    assert(index2 == index1); // If this assertion fails, the experiment is invalid. 

    LPVOID value2 = ::TlsGetValue(index2); 
    assert(value2 == 0); // If the TlsAlloc documentation is right, value2 == 0. 
         // If it's wrong, you'd expect value2 == 0x1234ABCD. 
} 

ho eseguito l'esperimento su Windows 7, con entrambi i programmi di test a 32 e 64 bit, compilato con VS 2010.

I risultati supportano l'idea che TlsAlloc re-inizializzi il valore a 0. Suppongo che TlsAlloc stia facendo qualcosa di zoppo come azzerare il valore solo per il thread corrente, ma la documentazione dice esplicitamente "slot" (plurale), quindi sembra sicuro supporre che se il thread non ha ancora utilizzato uno slot, il valore sarà 0.

Aggiornamento: Ulteriori sperimentazioni suggeriscono che è TlsFree piuttosto che TlsAlloc che reinizializza gli slot a 0.Quindi, come ha sottolineato Raymond, c'è ancora il rischio che un altro componente chiamato TlsSetValue dopo libera uno slot. Questo sarebbe un bug nell'altro componente, ma influenzerebbe il tuo codice.

Problemi correlati