如何在C ++ 11中获取整数线程ID


84

c ++ 11可能会获取当前的线程ID,但是它不能转换为整数类型:

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'

我真的不想做指针转换以获得整数线程ID。有某种合理的方法(因为我希望它是便携式的,所以是标准的)吗?


13
您需要什么作为整数?可以保证对它进行任何形式的算术运算都没有意义,并且在过程上下文之外没有意义,因此除了调试(operator<<看起来可以很好地处理)之外,无需对其进行序列化。
hmakholm在2011年

4
像这样的东西:1024cores.net/home/lock-free-algorithms/false-sharing---false,但不是N = MAX_THREAD_COUNT,而是N = 128并执行thread_id%N
NoSenseEtAl 2011年

9
如果您真的希望它具有可移植性,那么您需要为完全thread::id没有用整数表示的可能性做好准备。链接到的页面使用按线程ID索引的数组。您是否考虑过使用a map<thread::id, int>?然后,您可以使用已经为id该类定义的关系运算符,而无需进行任何转换。该标准还定义了hash<thread::id>,因此您也可以使用无序容器。
罗伯·肯尼迪

3
@Rob该地图需要静音:(
NoSenseEtAl 2011年

1
@SwissFrank还是应该说CHF:PI仍然存在,但是我认为可以接受的答案对我来说是确定的,这取决于我,确保变量id值在程序运行期间是唯一的。
NoSenseEtAl

Answers:


33

可移植的解决方案是将您自己生成的ID传递给线程。

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值映射到自己的ID,并在线程之间共享此映射(具有适当的同步),而不是直接传递ID。


1
啊哈!但有一个文本表示!对人类来说,在视觉上找到它们之间的区别就足够了,对吗?
徐妮

这里提到的thread :: id(或this_thread :: get_id())解决方案是最好的,因为它不是特定于程序员的。请参阅下面的Mike的stringstream答案,以获取字符串或整数表示形式。
安德鲁

@Andrew在回答中回答了这一问题:“即使运算符<<产生的文本表示形式也未指定,因此您不能依靠它作为数字表示形式”。似乎“最佳”一词的定义已不复存在。
R. Martinho Fernandes

“最佳”与字符串表示形式无关。
安德鲁

1
另外,我只是为自己着想进行了10,000,000次迭代的基准测试,并且this_thread :: get_id()实在是太快了:pastebin.com/eLa3rKQE调试模式每次调用花费0.0000002543827秒,对我而言,释放模式每次调用花费0.00000003652367秒。(英特尔i5 2.60 GHz)
安德鲁

85

你只需要做

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

得到一个size_t

来自cppreference

的模板专门std::hashstd::thread::id类允许用户获得线程的标识符的哈希值。


35
我认为这一定是std::hash<std::thread::id>()(std::this_thread::get_id()),不是吗?
巴里

12
哈希可以保证唯一吗?可能不是,打败它作为唯一线程标识符的用途。
Michael Goldshteyn

2
给出的示例至少不适用于Clang 3.4和libstdc ++ 4.8。但是,巴里的重新制定确实有效。
Arto Bendiken 2014年

3
谢谢888的回答。MS编译器确实具有thread :: id :: hash(),但Barry的代码符合标准。哈希可能会冲突。每个线程具有哈希值(希望冲突概率接近0)仍然很有用
a.lasram

1
在这种情况下,MSVC实际上会返回哈希线程ID。您也可以生成自己的...
rustyx

25

另一个id(idea?^^)将使用stringstreams:

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

如果在发生错误的情况下不希望出现异常,请使用try catch。


2
好答案。一般而言,这将达到目的。
iammilind

5
这不是可移植的,因为不能保证将std::thread::id打印成组成整数的字符,这与不能保证线程id在内部由整数表示的方式几乎相同。
blubberdiblub

1
每当实现选择整数不足时,@ Nikos。或者,只要它认为由于其他任何原因而不合适。这里的要点是,当规范没有将其指定为整数(并且它没有,它只是具有一些抽象保证)时,您不能也不应在任何实现中都将其视为整数。只需使用std::thread::idtype而不是一些整数,这就是它的用途。并且不要将其字符串表示形式重新解释为组成数字的数字。将其视为不透明或调试/日志输出。
blubberdiblub19

6

一种想法是使用线程本地存储来存储变量-不管什么类型,只要它符合线程本地存储的规则即可-然后使用该变量的地址作为您的“线程ID”。显然,任何算术运算都是没有意义的,但它将是一个不可或缺的类型。

对于后代: pthread_self()返回apid_t和posix。对于可移植性的某些定义,这是可移植的。

gettid(),几乎可以肯定它不是可移植的,但是它确实返回了GDB友好值。


pthread_self()实际上返回的pthread_t是不透明的(不同于pid_t(由返回gettid()),尽管它也是特定于平台的,但显然至少是整数)。但是首先+1,它解决了我的问题!
卡梅伦

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就是答案……编辑:

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];可能是罚款。(用于非常轻量级同步的原子或自旋锁。)
Scott Lamb

@R。Martinho Fernandes就像我说的那样,我对int值感兴趣,因此我可以做到这一点,如果冲突很少见,就可以了,基本上是Scott所说的。
NoSenseEtAl 2011年

1
我实际上尝试了这一点,但我完全错了-即使没有争用,只是使用atomic<int>而不是int也会大大降低速度。
Scott Lamb

1
您可以使用诸如ideone.com/Q7Nh4之类的东西替换static_assert (如果需要,可以轻松地调整以强制执行确切的大小要求),使其更具可移植性(例如,请注意ideone如何具有32位线程ID) 。
R. Martinho Fernandes

4

thread::native_handle()返回thread::native_handle_type,这是typedef long unsigned int

如果线程是默认构造的,则native_handle()返回0。如果有附加的OS线程,则返回值为非零(在POSIX上为pthread_t)。


在哪里指定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来产生故意的编译错误。然后,编译器会抱怨类型不匹配,并且还会告诉您类型是什么。
Alexey Polonsky '18

1
嗯,这是不可移植的,因为它依赖于特定的实现。
Ruslan '18

好吧,至少如果基础实现使用POSIX pthread,看来native_handle()必须是pthread_t。现在,pthread_t是指针类型(typedef struct pthread * pthread_t)。因此,std :: thread :: native_handle_type是能够包含指针的整数类型(例如size_t或unsigned long)是有意义的。
Alexey Polonsky '18

3

这样,应该工作:

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

切记要包含库sstream


很好,但是为什么要假定它是整数?可以是十六进制或其他任何形式。
rustyx

如果使用std::stringstream,则可以使用将其operator >>转换为int。实际上,如果我确定不可或缺,那么我宁愿将其uint64_t作为的类型。idintid
aniliitb10

3

不使用thread :: get_id()的主要原因是它在单个程序/进程中不是唯一的。这是因为一旦第一个线程完成,id就可以被第二个线程重用。

这似乎是一个可怕的功能,但在c ++ 11中却是什么。


2

这取决于您要使用thread_id的用途;您可以使用:

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

这将为您的过程生成一个唯一的ID。但是有一个局限性:如果您启动同一进程的多个实例,并且每个实例将其线程ID写入一个公共文件,则无法保证thread_id的唯一性;实际上,您很可能会重叠。在这种情况下,您可以执行以下操作:

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

现在,您将获得系统范围内唯一的线程ID。


重载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。该staticthread_counter是因为你不希望暴露它这个编译单元外部。
9502年

这种奇怪的方式是按查询线程ID的顺序分配线程ID。(我自己做过非常类似的事情,而且我从来不喜欢这种怪异。)它也从零开始赋值,这是不常见的。(例如,GDB报告的线程ID从1开始。)
Swiss Frank

1
@SwissFrank:这只是一个数字,您不应在返回的值中读取太多:查询时没有合法的方法知道它是分配的:-)。关于这0是一个有效的ID,这一点很重要,可以使用预增量进行修复。我将更改答案来做到这一点。
6502

1

也许此解决方案对某人有帮助。第一次称呼它为im 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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.