Come ottenere un ID thread intero in c ++ 11


88

c ++ 11 ha la possibilità di ottenere l'id del thread corrente, ma non è castable al tipo intero:

cout<<std::this_thread::get_id()<<endl;

uscita: 139918771783456

cout<<(uint64_t)std::this_thread::get_id()<<endl;

errore: cast non valido dal tipo "std :: thread :: id" al tipo "uint64_t" stesso per altri tipi: cast non valido dal tipo "std :: thread :: id" al tipo "uint32_t"

Non voglio davvero eseguire il casting del puntatore per ottenere l'id del thread intero. C'è un modo ragionevole (standard perché voglio che sia portatile) per farlo?


13
Per cosa hai bisogno che sia un numero intero? È garantito che non ha senso eseguire operazioni aritmetiche di alcun tipo su di esso, e non è significativo al di fuori del contesto del processo, quindi non dovrebbe essere necessario serializzarlo se non per il debug (che operator<<sembra gestire bene).
hmakholm ha lasciato Monica il

4
qualcosa del genere: 1024cores.net/home/lock-free-algorithms/false-sharing---false ma invece di N = MAX_THREAD_COUNT avrò qualcosa come N = 128 e farò thread_id% N
NoSenseEtAl

9
Se vuoi davvero che sia portabile, devi essere preparato per la possibilità che thread::idnon sia rappresentata affatto come un numero intero. La pagina a cui ti colleghi utilizza un array, indicizzato dall'ID thread. Hai pensato di utilizzare un map<thread::id, int>invece? Quindi puoi utilizzare gli operatori relazionali già definiti per la idclasse senza effettuare alcuna conversione. Lo standard definisce anche hash<thread::id>, quindi puoi usare anche i contenitori non ordinati.
Rob Kennedy

3
@ Rob quella mappa richiederebbe il mutexing :(
NoSenseEtAl

1
@SwissFrank o dovrei dire CHF: PI sono ancora in giro, ma penso che la risposta accettata sia ok per me, spetta a me assicurarmi che i valori di ID variabili siano unici per la durata di un programma.
NoSenseEtAl

Risposte:


35

La soluzione portatile consiste nel passare i propri ID generati nel thread.

int id = 0;
for(auto& work_item : all_work) {
    std::async(std::launch::async, [id,&work_item]{ work_item(id); });
    ++id;
}

Il std::thread::idtipo deve essere utilizzato solo per i confronti, non per l'aritmetica (cioè come si dice sulla lattina: un identificatore ). Anche la sua rappresentazione testuale prodotta da nonoperator<< è specificata , quindi non puoi fare affidamento sul fatto che sia la rappresentazione di un numero.

Puoi anche usare una mappa di std::thread::idvalori per il tuo id e condividere questa mappa (con la corretta sincronizzazione) tra i thread, invece di passare direttamente l'id.


1
Aha! Ma v'è una rappresentazione di testo! È abbastanza buono per gli umani per trovare visivamente la distinzione tra loro, giusto?
Xunie

La soluzione thread :: id (o this_thread :: get_id ()) menzionata qui è la migliore, perché non è specifica del programmatore. Vedi la risposta del flusso di strings di Mike di seguito per ottenere una rappresentazione di stringa o intera.
Andrew

@Andrew ho affrontato questo nella risposta: "Anche la sua rappresentazione testuale prodotta dall'operatore << non è specificata, quindi non puoi fare affidamento sul fatto che sia la rappresentazione di un numero". Sembra che una definizione losca della parola "migliore" sia a portata di mano.
R. Martinho Fernandes

"migliore" non era in relazione alla rappresentazione della stringa.
Andrew

1
Inoltre, ho appena eseguito un benchmark con 10.000.000 di iterazioni per il mio bene e this_thread :: get_id () è incredibilmente veloce: pastebin.com/eLa3rKQE La modalità di debug richiede 0,0000002543827 secondi per chiamata e il rilascio richiede 0,00000003652367 secondi per chiamata per me. (Intel i5 2.60 GHz)
Andrew

89

Hai solo bisogno di fare

std::hash<std::thread::id>{}(std::this_thread::get_id())

per ottenere un file size_t.

Da cppreference :

La specializzazione del modello di std::hashper la std::thread::idclasse consente agli utenti di ottenere hash degli identificatori dei thread.


35
Penso che debba essere così std::hash<std::thread::id>()(std::this_thread::get_id()), vero?
Barry

12
L'hashish sarebbe garantito unico? Probabilmente no, vanificando il suo utilizzo come identificatore di thread univoco.
Michael Goldshteyn

2
L'esempio fornito non funziona almeno con Clang 3.4 e libstdc ++ 4.8. La riformulazione di Barry funziona, tuttavia.
Arto Bendiken

3
grazie 888 per la risposta. Il compilatore MS ha thread :: id :: hash () ma il codice di Barry è conforme agli standard. Gli hash possono entrare in collisione. È ancora utile avere un hash per thread (con si spera una probabilità di collisione prossima a 0)
a.lasram

1
MSVC restituisce effettivamente un ID thread con hash in questo caso. Potresti anche generare il tuo ...
rustyx

26

Un altro id (idea? ^^) potrebbe essere quello di utilizzare stringstream:

std::stringstream ss;
ss << std::this_thread::get_id();
uint64_t id = std::stoull(ss.str());

E usa try catch se non vuoi un'eccezione nel caso in cui le cose vadano storte ...


2
Buona risposta. Ciò servirebbe allo scopo in generale.
iammilind

5
Questo non è portabile, poiché non è garantito che a std::thread::idstampa come caratteri che compongono un numero intero nello stesso modo in cui non è garantito che l'id del thread sia rappresentato internamente da un intero.
blubberdiblub

1
@Nikos ogni volta che un'implementazione sceglie che un numero intero è insufficiente. O ogni volta che lo ritenga inappropriato per qualsiasi altro motivo. Il punto qui è che quando la specifica non lo specifica come un intero (e non lo fa, ha solo alcune garanzie più astratte), non puoi e non dovresti fare affidamento sul fatto che sia un numero intero in nessuna implementazione. Usa semplicemente std::thread::idcome tipo invece di un numero intero, è per questo che esiste. E non reinterpretare la sua rappresentazione di stringa come cifre che compongono un numero. Trattalo come opaco o come output di debug / registrazione.
blubberdiblub

6

Un'idea potrebbe essere quella di utilizzare la memoria locale del thread per memorizzare una variabile - non importa di che tipo, purché sia ​​conforme alle regole della memorizzazione locale del thread - quindi utilizzare l'indirizzo di quella variabile come "ID thread". Ovviamente qualsiasi aritemetica non sarà significativa, ma sarà un tipo integrale.

Per i posteri: pthread_self()restituisce a pid_ted è posix. Questo è portabile per alcune definizioni di portabile.

gettid(), quasi certamente non portabile, ma restituisce un valore amichevole GDB.


pthread_self()in realtà restituisce a pthread_t, che è opaco (a differenza di pid_t(restituito da gettid()) che, sebbene anche specifico della piattaforma, è apparentemente un numero intero, almeno). Ma +1 per il primo bit, ha risolto il mio problema!
Cameron

4

Non so davvero quanto sia veloce, ma questa è la soluzione che sono riuscito a stimare:

const size_t N_MUTEXES=128;//UINT_MAX,not 128  for answer to my original question
hash<std::thread::id> h;
cout<<h(std::this_thread::get_id())%N_MUTEXES<<endl;

Ancora una volta sto iniziando a pensare che ottenere un puntatore alla struttura e lanciarlo su unsigned int o uint64_t sia la risposta ... EDIT:

uint64_t get_thread_id()
{
    static_assert(sizeof(std::thread::id)==sizeof(uint64_t),"this function only works if size of thead::id is equal to the size of uint_64");
    auto id=std::this_thread::get_id();
    uint64_t* ptr=(uint64_t*) &id;
    return (*ptr);
}
int main()
{
    cout<<std::this_thread::get_id()<<"  "<<get_thread_id()<<endl;
}

static_assert per prevenire problemi infernali :) Riscrivere è facile rispetto alla ricerca di questo tipo di bug. :)


3
Non hai garanzie che non otterrai valori duplicati con la hashfunzione, tanto meno se la% .
R. Martinho Fernandes

1
Non puoi ottenere quella garanzia con std::this_thread::get_id()! Ma probabilmente non ne hai bisogno. Un paio di thread condivisi tra loro non creano lo stesso enorme problema di ogni thread condiviso con ogni altro thread. Qualcosa del genere const size_t N_COUNTERS = 128; struct Counter { std::atomic<int> counter; char pad[CACHE_LINE_SIZE - sizeof(atomic<int>); } counters[N_COUNTERS];probabilmente va bene. (Un atomico o spinlock per una sincronizzazione molto leggera.)
Scott Lamb

@R. Martinho Fernandes Come ho detto, sono interessato al valore int, quindi posso% it, le collisioni sono ok se sono rare, in pratica quello che ha detto Scott.
NoSenseEtAl

1
In realtà l'ho provato e mi sbagliavo completamente: il solo utilizzo atomic<int>invece di intè un drammatico rallentamento anche senza contese.
Scott Lamb

1
Puoi sostituire static_assert con qualcosa come questo ideone.com/Q7Nh4 (facilmente modificabile per applicare un requisito di dimensione esatta se lo desideri invece) per farlo funzionare in modo più portabile (nota come ideone ha un ID thread a 32 bit, per esempio) .
R. Martinho Fernandes

4

thread::native_handle()restituisce thread::native_handle_type, che è un typedef per long unsigned int.

Se il thread è costruito in modo predefinito, native_handle () restituisce 0. Se c'è un thread del sistema operativo collegato ad esso, il valore restituito è diverso da zero (è pthread_t su POSIX).


Dove viene specificato che std::thread::native_handle_typeè un typedef long unsigned? In 30.3.1 / 1 possiamo solo vederetypedef implementation-defined native_handle_type; // See 30.2.3
Ruslan

Un modo stupido ma semplice per scoprire il tipo è generare un errore di compilazione intenzionale assegnando thread :: native_handle () ad es. Uint8_t. Quindi il compilatore si lamenterà della mancata corrispondenza del tipo e ti dirà anche qual è il tipo.
Alexey Polonsky

1
Beh, non è portatile poiché si basa su un'implementazione particolare.
Ruslan,

Bene, almeno se l'implementazione sottostante usa pthread POSIX, sembra che native_handle () debba essere un pthread_t. Ora, pthread_t è un tipo di puntatore (typedef struct pthread * pthread_t). Quindi, ha senso che std :: thread :: native_handle_type sia un tipo intero in grado di contenere un puntatore (ad esempio size_t o unsigned long).
Alexey Polonsky

3

In questo modo, dovrebbe funzionare:

std::stringstream ss;
ss << std::this_thread::get_id();
int id = std::stoi(ss.str());

Ricorda di includere la libreria sstream


Bello, ma perché presumi che sia un numero intero? Può essere esadecimale o qualsiasi altra cosa.
rustyx

se stai usando std::stringstream, puoi usarlo operator >>per convertirlo in int. In realtà preferirei uint64_tcome tipo idinvece che intse sono sicuro che idsia integrale.
aniliitb10

3

Un motivo fondamentale per non utilizzare thread :: get_id () è che non è unico per un singolo programma / processo. Questo perché l'id può essere riutilizzato per un secondo thread, al termine del primo thread.

Questa sembra una caratteristica orribile, ma è ciò che è in c ++ 11.


2

dipende da cosa vuoi usare per thread_id; Puoi usare:

std::stringstream ss;
ss << std::this_thread::get_id();
uint64_t id = std::stoull(ss.str());

Questo genererà un ID univoco durante il processo; ma c'è una limitazione: se avvii più istanze dello stesso processo e ognuna di esse scrive i propri thread id in un file comune, l'unicità di thread_id non è garantita; infatti è molto probabile che avrai delle sovrapposizioni. In questo caso puoi fare qualcosa come:

#include <sys/time.h>
timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
uint64_t id = (ts.tv_sec % 1000000000) * 1000000000 + ts.tv_nsec;

ora ti vengono garantiti ID thread univoci in tutto il sistema.


Il sovraccarico operator<<può stampare qualsiasi cosa , è sbagliato presumere che stamperà sempre un numero intero.
rustyx

2

Un'altra alternativa:

#include <atomic>

static std::atomic<unsigned long long> thread_counter;

unsigned long long thread_id() {
    thread_local unsigned long long tid = ++thread_counter;
    return tid;
}

Il codice generato per questa funzione da g ++ in x86 a 64 bit è semplicemente:

_Z9thread_idv:
        cmp     BYTE PTR fs:_ZGVZ9thread_idvE3tid@tpoff, 0
        je      .L2
        mov     rax, QWORD PTR fs:_ZZ9thread_idvE3tid@tpoff
        ret
.L2:
        mov     eax, 1
        lock xadd       QWORD PTR _ZL14thread_counter[rip], rax
        mov     BYTE PTR fs:_ZGVZ9thread_idvE3tid@tpoff, 1
        mov     QWORD PTR fs:_ZZ9thread_idvE3tid@tpoff, rax
        ret
_ZGVZ9thread_idvE3tid:
        .zero   8
_ZZ9thread_idvE3tid:
        .zero   8

Cioè un singolo ramo senza alcuna sincronizzazione che verrà correttamente previsto tranne che per la prima volta che chiami la funzione. Dopodiché solo un singolo accesso alla memoria senza sincronizzazione.


@NoSenseEtAl: Non sono sicuro di aver capito la tua domanda ... thread_localdescrive già la durata dell'archiviazione per tid. Il staticper thread_counterè perché non si vuole esporre al di fuori di questa unità di compilazione.
6502

Questo tipo di assegna stranamente gli ID del thread nell'ordine in cui interroghi l'ID del thread. (Anch'io ho fatto qualcosa di MOLTO simile e non mi è mai piaciuta questa stranezza.) Assegna anche da zero, il che non è normale. (Ad esempio, GDB riporta gli ID dei thread a partire da 1.)
Swiss Frank

1
@SwissFrank: è solo un numero e non dovresti leggere troppo nel valore restituito: non esiste un modo legale per sapere che è stato assegnato quando lo hai richiesto :-). Circa il fatto che 0sia un ID valido, questo è un buon punto e può essere risolto usando invece il preincremento. Cambierò la risposta per farlo.
6502

1

Forse questa soluzione può essere utile a qualcuno. Chiamalo una prima volta im main(). Attenzione: namescresce indefinitamente.

std::string currentThreadName(){
    static std::unordered_map<std::thread::id,std::string> names;
    static std::mutex mtx;

    std::unique_lock<std::mutex> lock(mtx);

    auto id = std::this_thread::get_id();

    if(names.empty()){
        names[id] = "Thread-main";
    } else if(names.find(id) == names.end()){
        std::stringstream stream;
        stream << "Thread-" << names.size();
        names[id] = stream.str();
    }

    return names[id];
}

non usare stringstream, è lento, usa std :: to_string
NoSenseEtAl
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.