Як отримати цілочисельний ідентифікатор потоку в c ++ 11


84

c ++ 11 має можливість отримати поточний ідентифікатор потоку, але його не можна передати цілочисельному типу:

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

вихід: 139918771783456

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

помилка: недійсне приведення з типу 'std :: thread :: id' до типу 'uint64_t' те саме для інших типів: недійсне приведення з типу 'std :: thread :: id' до типу 'uint32_t'

Я дійсно не хочу робити кастинг покажчика, щоб отримати цілочисельний ідентифікатор потоку. Чи є якийсь розумний спосіб (стандартний, оскільки я хочу, щоб він був портативним), щоб це зробити?


13
Для чого це потрібно, щоб воно було цілим числом? Гарантовано не має сенсу робити будь-яку арифметику на ній, і це не має сенсу поза контекстом процесу, тому не повинно бути потреби в серіалізації, крім налагодження (яке, operator<<схоже, добре справляється).
hmakholm залишив Моніку

4
приблизно так: 1024cores.net/home/lock-free-algorithms/false-sharing---false, але замість N = MAX_THREAD_COUNT я буду мати щось на кшталт N = 128 і робити thread_id% N
NoSenseEtAl

9
Якщо ви дійсно хочете, щоб він був портативним, то вам слід бути готовим до можливості, яка thread::idвзагалі не представляється цілим числом. Сторінка, на яку ви посилаєтесь, використовує масив, індексований ідентифікатором потоку. Ви думали використовувати map<thread::id, int>замість цього? Тоді ви можете використовувати реляційні оператори, вже визначені для idкласу, не виконуючи жодних перетворень. Стандарт також визначає hash<thread::id>, тому ви можете використовувати і невпорядковані контейнери.
Роб Кеннеді,

3
@Rob, що на цій карті буде потрібно мутексинг :(
NoSenseEtAl

1
@SwissFrank або я повинен сказати CHF: PI все ще поруч, але я вважаю, що прийнята відповідь для мене нормальна, це від мене залежить, щоб значення змінних id були унікальними протягом тривалості програми.
NoSenseEtAl

Відповіді:


33

Портативне рішення полягає у передачі ваших власних згенерованих ідентифікаторів у потік.

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

std::thread::idТип повинен використовуватися для порівняння, а не для арифметичної (тобто , як він каже на балончику: ідентифікатор ). Навіть його текстове представлення operator<<не визначене , тому ви не можете покладатися на те, що це представлення числа.

Ви також можете використовувати карту std::thread::idзначень для власного ідентифікатора та ділитися цією картою (з відповідною синхронізацією) між потоками, замість того, щоб передавати ідентифікатор безпосередньо.


1
Ага! Але є подання тексту! Це досить добре, щоб люди візуально знаходили між собою різницю, так?
Сюні

Зазначене тут рішення thread :: id (або this_thread :: get_id ()) найкраще, оскільки воно не залежить від програміста. Див. Відповідь Майка на рядок нижче, щоб отримати представлення рядка або цілого числа.
Ендрю

@Andrew Я звернувся до цього у відповіді: "Навіть його текстове представлення, створене оператором <<, не визначене, тому ви не можете покладатися на це як представлення числа". Здається, під рукою є тіньове визначення слова "найкращий".
Р. Мартіньо Фернандес

"найкраще" не було відносно рядкового подання.
Ендрю

1
Крім того, я щойно зробив бенчмарк із 10 000 000 ітерацій заради себе, і this_thread :: get_id () швидко злий: pastebin.com/eLa3rKQE Режим налагодження займає 0,0000002543827 секунд на дзвінок, а Release - 0,00000003652367 секунд на дзвінок для мене. (Intel i5 2,60 ГГц)
Ендрю

85

Вам просто потрібно це зробити

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

отримати a size_t.

З cppreference :

Спеціалізація шаблону std::hashдля std::thread::idкласу дозволяє користувачам отримувати хеші ідентифікаторів потоків.


35
Я думаю, це має бути std::hash<std::thread::id>()(std::this_thread::get_id()), правда?
Barry

12
Чи гарантований хеш буде унікальним? Можливо, ні, перемігши його використання як унікальний ідентифікатор потоку.
Michael Goldshteyn

2
Наведений приклад не працює принаймні з Clang 3.4 та libstdc ++ 4.8. Однак переформулювання Баррі працює.
Арто Бендікен

3
дякую 888 за відповідь. У компіляторі MS є thread :: id :: hash (), але код Баррі відповідає стандартам. Хеші можуть зіткнутися. Ще корисно мати хеш для кожного потоку (з надією, що ймовірність зіткнення близько 0)
a.lasram

1
MSVC фактично повертає хешований ідентифікатор потоку в цьому випадку. Ви можете також створити свій власний ...
rustyx

25

Іншим ідентифікатором (ідея? ^^) було б використання потокових рядків:

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

І використовуйте спробу catch, якщо ви не хочете винятку, якщо щось піде не так ...


2
Гарна відповідь. Це могло б служити цілі загалом.
iammilind

5
Це не є портативним, оскільки немає гарантії, що std::thread::idдрук здійснюється як символи, що складають ціле число, приблизно так само, як не гарантується, що ідентифікатор потоку внутрішньо представлений цілим числом.
blubberdiblub

1
@Nikos щоразу, коли реалізація вирішує, що ціле число недостатнє. Або коли він вважає це недоречним з будь-якої іншої причини. Справа тут у тому, що коли специфікація не визначає його як ціле число (і ні, воно має лише деякі абстрактні гарантії), ви не можете і не повинні покладатися на це ціле число у будь-якій реалізації. Просто використовуйте std::thread::idяк тип замість деякого цілого числа, для цього воно існує. І не переосмислюйте його рядкове представлення як цифри, що складають число. Розгляньте це як непрозорий або як вивід налагодження / реєстрації.
blubberdiblub

6

Одна ідея полягала б у використанні потокової локальної пам’яті для зберігання змінної - неважливо, якого типу, якщо вона відповідає правилам локальної пам’яті потоку, - а потім використовувати адресу цієї змінної як ваш „ідентифікатор потоку”. Очевидно, що будь-яка арифметика не матиме значення, але вона буде цілісним типом.

Для нащадків: pthread_self()повертає a pid_tі є posix. Це переносно для деякого визначення переносного.

gettid(), майже напевно не портативний, але він повертає дружнє значення GDB.


pthread_self()фактично повертає a pthread_t, який є непрозорим (на відміну від pid_t(повертається gettid()), який, хоча і є певним для платформи, є, принаймні, цілим числом). Але +1 для першого біта, це вирішило мою проблему!
Cameron

4

Я справді не знаю, наскільки це швидко, але це рішення, яке мені вдалося запросити в гості:

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;

Знову я починаю думати, що отримати вказівник на структуру та передати його в unsigned int або uint64_t - це відповідь ... 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 для запобігання пекельним проблемам :) Переписати це легко порівняно з вишукуванням подібних помилок. :)


3
Ви не маєте жодних гарантій, що не отримаєте повторюваних значень за допомогою hashфункції, а тим більше, якщо ви її% .
R. Martinho Fernandes

1
Ви не можете отримати цю гарантію std::this_thread::get_id()! Але вам це, мабуть, не потрібно. Пара обмінних потоків між собою не створює такої ж масивної проблеми, як обмін усіма потоками з усіма іншими потоками. Щось подібне const size_t N_COUNTERS = 128; struct Counter { std::atomic<int> counter; char pad[CACHE_LINE_SIZE - sizeof(atomic<int>); } counters[N_COUNTERS];, мабуть, добре. (Атомний або спін-блок для дуже легкої синхронізації.)
Скотт Лемб,

@R. Мартіньо Фернандес Як я вже сказав, що мене цікавить значення int, тому я можу це зробити, зіткнення - це нормально, якщо вони рідкісні, в основному те, що сказав Скотт.
NoSenseEtAl

1
Я насправді спробував це, і я був абсолютно неправий - просто використання atomic<int>замість цього int- це значне уповільнення навіть без суперечок.
Скотт Лемб

1
Ви можете замінити static_assert чимось на зразок цього ideone.com/Q7Nh4 (його легко налаштувати, щоб застосувати точні вимоги до розміру, якщо ви цього хочете), щоб він працював портативніше (наприклад, зверніть увагу, як Ideone має 32-розрядний ідентифікатор потоку, наприклад) .
R. Martinho Fernandes

4

thread::native_handle()повертається thread::native_handle_type, що є typedef для long unsigned int.

Якщо потік побудований за замовчуванням, native_handle () повертає 0. Якщо до нього приєднаний потік ОС, значення повернення є ненульовим (це pthread_t на POSIX).


Де вказано, для чого std::thread::native_handle_typeпризначений typedef long unsigned? У 30.3.1 / 1 ми можемо побачити лишеtypedef implementation-defined native_handle_type; // See 30.2.3
Руслан

Тупий, але простий спосіб виявити тип - це генерація навмисної помилки компіляції, присвоївши thread :: native_handle (), наприклад, uint8_t. Тоді компілятор скаржиться на невідповідність типу, а також повідомляє, що це за тип.
Олексій Полонський

1
Ну, це не портативний, оскільки він покладається на конкретну реалізацію.
Руслан

Ну, принаймні, якщо основна реалізація використовує POSIX pthread, здається, що native_handle () повинен бути pthread_t. Тепер pthread_t - це тип вказівника (typedef struct pthread * pthread_t). Отже, має сенс, що std :: thread :: native_handle_type - це цілий тип, здатний містити покажчик (наприклад, size_t або unsigned long).
Олексій Полонський

3

Таким чином, повинні працювати:

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

Не забудьте включити бібліотечний потік


Приємно, але чому ви вважаєте, що це ціле число? Це може бути шістнадцятковий або що-небудь ще.
rustyx

якщо ви використовуєте std::stringstream, то ви можете використовувати його operator >>для перетворення в int. Насправді я віддав би перевагу uint64_tтипу типу, idа не тому, intякщо впевнений, що idце невід’ємне.
aniliitb10

3

Ключовою причиною не використовувати thread :: get_id () є те, що він не є унікальним для однієї програми / процесу. Це тому, що ідентифікатор може бути використаний повторно для другого потоку, як тільки перший потік закінчиться.

Це здається жахливою особливістю, але це те, що в C ++ 11.


2

це залежить від того, для чого ви хочете використовувати thread_id; Ви можете використовувати:

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

Це генерує унікальний ідентифікатор у процесі; але є обмеження: якщо ви запускаєте кілька екземплярів одного і того ж процесу, і кожен з них записує свої ідентифікатори потоків у загальний файл, унікальність thread_id не гарантується; насправді дуже ймовірно, що у вас будуть дублювання. У цьому випадку ви можете зробити щось на зразок:

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

тепер вам гарантовано унікальні ідентифікатори потоків у всій системі.


Перевантажений operator<<може друкувати що завгодно , неправильно вважати, що він завжди буде друкувати ціле число.
rustyx

2

Інша альтернатива:

#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;
}

Згенерований код для цієї функції g ++ у x86 64-біт:

_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

Тобто одна гілка без будь-якої синхронізації, яка буде правильно передбачена, за винятком першого виклику функції. Після цього лише один доступ до пам'яті без синхронізації.


@NoSenseEtAl: Не впевнений, що я розумію ваше запитання ... thread_localвже описує тривалість зберігання для tid. staticДля thread_counterтому , що ви не хочете , щоб виставити його за межами цього модуля компіляції.
6502

Цей тип дивним чином присвоює ідентифікатори потоків у тому порядку, в якому ви запитуєте ідентифікатор потоку. (Я сам зробив ДУЖЕ подібне, і мені ця дивина ніколи не подобалася.) Це також призначається з нуля, що не є звичним. (Наприклад, GDB повідомляє ідентифікатори потоків, починаючи з 1.)
швейцарський франк

1
@SwissFrank: це просто число, і ви не повинні читати занадто багато у поверненому значенні: не існує законного способу дізнатися, що воно було призначене, коли ви його запитували :-). Про те, що 0це дійсний ідентифікатор, який є хорошим моментом і може бути виправлений за допомогою попереднього збільшення. Я зміню відповідь, щоб зробити це.
6502

1

Можливо, це рішення буде комусь корисним. Назвіть це вперше ім main(). Попередження: namesзростає нескінченно.

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];
}

не використовуйте stringstream, це повільно, використовуйте std :: to_string
NoSenseEtAl
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.