【问题标题】:How to print pthread_t如何打印 pthread_t
【发布时间】:2010-12-18 02:29:17
【问题描述】:

已搜索,但没有找到令人满意的答案。

我知道没有可移植的方式来打印 pthread_t。

你是如何在你的应用中做到这一点的?

更新:

实际上我不需要 pthread_t,而是一些小的数字 id,在调试消息中标识不同的线程。

在我的系统(64 位 RHEL 5.3)上,它被定义为 unsigned long int,所以它是一个很大的数字,仅仅打印它就在调试行中占据了一个有价值的位置。 gdb 如何分配短时间?

【问题讨论】:

    标签: c++ c linux pthreads


    【解决方案1】:

    这将打印出pthread_t 的十六进制表示,无论它实际上是什么:

    void fprintPt(FILE *f, pthread_t pt) {
      unsigned char *ptc = (unsigned char*)(void*)(&pt);
      fprintf(f, "0x");
      for (size_t i=0; i<sizeof(pt); i++) {
        fprintf(f, "%02x", (unsigned)(ptc[i]));
      }
    }
    

    要为每个 pthread_t 打印一个小 id,可以使用类似的东西(这次使用 iostreams):

    void printPt(std::ostream &strm, pthread_t pt) {
      static int nextindex = 0;
      static std::map<pthread_t, int> ids;
      if (ids.find(pt) == ids.end()) {
        ids[pt] = nextindex++;
      }
      strm << ids[pt];
    }
    

    根据平台和pthread_t 的实际表示,这里可能需要为pthread_t 定义一个operator&lt;,因为std::map 需要对元素进行排序:

    bool operator<(const pthread_t &left, const pthread_t &right) {
      ...
    }
    

    【讨论】:

    • +1 可移植性。但是这个解决方案产生了更长的 pthread_t 表示,它是数字“非便携式”解决方案。
    • gdb 分配一个短数字。我很高兴有一些类似的解决方案
    • R.佩特:不,没有。请注意,ptc 指针在 fprintf 行中被取消引用。
    • 在转换为unsigned char* 之前转换为void* 的目的是什么?为什么第一个演员不是多余的?
    【解决方案2】:

    GDB 在 Linux 上使用线程 ID(又名内核 pid,又名 LWP)作为短数字。试试:

      #include <syscall.h>
      ...
    
        printf("tid = %d\n", syscall(SYS_gettid));
    

    【讨论】:

    • 这将返回 LWP。也许我错了,但 pthread 可以在运行时绑定到不同的 LWP,因此不能唯一标识不同的 pthread
    • 在 Linux 上,pthread_t 和 LWP 之间存在 1:1 映射。您永远不会让同一个线程在其生命周期的不同时间点报告不同的 LWP。
    • 在 Linux 上将其称为 LWP 是 IMO 错误的。因为没有“轻”和“重”的重量过程。只有任务,即可调度的实体,可以共享各种资源。这就是这个特定于 Linux 的系统调用返回的内容,task id。
    • 谢谢!使用 %lu 避免带有 gcc -Wall 的警告
    【解决方案3】:

    在这种情况下,它取决于操作系统,因为the POSIX standard no longer requires pthread_t to be an arithmetic type

    IEEE Std 1003.1-2001/Cor 2-2004, item XBD/TC2/D6/26 被应用,将pthread_t添加到不需要是算术类型的类型列表中,从而允许pthread_t定义为结构。

    您需要查看sys/types.h 标头并查看pthread_t 是如何实现的;然后您可以按照您认为合适的方式打印它。由于没有可移植的方式来执行此操作,并且您没有说明您使用的是什么操作系统,所以没有太多要说的了。

    编辑:回答您的新问题,GDB assigns its own thread ids each time a new thread starts

    出于调试目的,gdb 将其自己的线程号(始终为单个整数)与程序中的每个线程相关联。

    如果您希望在每个线程内打印一个唯一编号,您最简洁的选择可能是告诉每个线程在启动时使用什么编号。

    【讨论】:

    • 线程本地存储变量也可以工作。您也可以推迟分配数字,直到给定线程需要,但这涉及到特定于您的程序的权衡。
    • James:“你最干净的选择可能是告诉每个线程在你启动它时使用什么数字”——我刚刚浏览了pthread_create,我没有任何反应。如何做到这一点?
    • pthread_create 的第四个参数允许您将任意数据传递给线程过程。
    【解决方案4】:

    好的,看来这是我的最终答案。我们有两个实际问题:

    • 如何为日志记录线程获取更短的唯一 ID。
    • 无论如何,我们需要为线程打印真正的 pthread_t ID(至少只是为了链接到 POSIX 值)。

    1.打印 POSIX ID (pthread_t)

    您可以简单地将 pthread_t 视为字节数组,并为每个字节打印十六进制数字。因此,您不受某些固定尺寸类型的限制。唯一的问题是字节顺序。您可能喜欢打印字节的顺序是否与打印的简单“int”相同。以下是 little-endian 的示例,只有 order 应该为 big-endian 恢复(在定义下?):

    #include <pthread.h>
    #include <stdio.h>
    
    void print_thread_id(pthread_t id)
    {
        size_t i;
        for (i = sizeof(i); i; --i)
            printf("%02x", *(((unsigned char*) &id) + i - 1));
    }
    
    int main()
    {
        pthread_t id = pthread_self();
    
        printf("%08x\n", id);
        print_thread_id(id);
    
        return 0;
    }
    

    2。获取更短的可打印线程 ID

    在任何建议的情况下,您都应该将真实线程 ID (posix) 转换为某个表的索引。但是有两种截然不同的方法:

    2.1.跟踪线程。

    您可以跟踪表中所有现有线程的线程 ID(应包装它们的 pthread_create() 调用)并具有“重载”的 id 函数,该函数只为您获取表索引,而不是真正的线程 ID。该方案对于任何与内部线程相关的调试和资源跟踪也非常有用。明显的优势是线程级跟踪/调试工具的副作用,未来可能扩展。缺点是需要跟踪任何线程的创建/销毁。

    这里是部分伪代码示例:

    pthread_create_wrapper(...)
    {
       id = pthread_create(...)
       add_thread(id);
    }
    
    pthread_destruction_wrapper()
    {
       /* Main problem is it should be called.
          pthread_cleanup_*() calls are possible solution. */
       remove_thread(pthread_self());
    }
    
    unsigned thread_id(pthread_t known_pthread_id)
    {
      return seatch_thread_index(known_pthread_id);
    }
    
    /* user code */
    printf("04x", thread_id(pthread_self()));
    

    2.2.只需注册新的线程 ID。

    在记录过程中调用 pthread_self() 并搜索内部表是否知道线程。 如果创建了具有此类 ID 的线程,则使用其索引(或从以前的线程中重新使用,实际上这并不重要,因为同一时刻没有 2 个相同的 ID)。如果尚不知道线程 ID,则会创建新条目以便生成/使用新索引。

    优点是简单。缺点是没有跟踪线程创建/销毁。因此,要跟踪这一点,需要一些外部机制。

    【讨论】:

    • +1 用于查找表。我也在考虑这种方法。虽然我无法控制所有创建的线程,但它们都使用相同的功能在记录器中打印消息。所以我可以在日志时建立一个查找表。
    【解决方案5】:

    在 Centos 5.4 x86_64 上,pthread_t 是 unsigned long 的 typedef。

    因此,我们可以这样做...

    #include <iostream>
    #include <pthread.h>
    
    int main() {
        pthread_t x;
        printf("%li\n", (unsigned long int) x);
        std::cout << (unsigned long int) x << "\n";
    }
    

    【讨论】:

      【解决方案6】:

      你可以这样做:

      int thread_counter = 0;
      pthread_mutex_t thread_counter_lock = PTHREAD_MUTEX_INITIALIZER;
      
      int new_thread_id() {
          int rv;
          pthread_mutex_lock(&thread_counter_lock);
          rv = ++thread_counter;
          pthread_mutex_unlock(&thread_counter_lock);
          return rv;
      }
      
      static void *threadproc(void *data) {
          int thread_id = new_thread_id();
          printf("Thread %d reporting for duty!\n", thread_id);
          return NULL;
      }
      

      如果您可以依赖 GCC(clang 在这种情况下也可以使用),您也可以这样做:

      int thread_counter = 0;
      
      static void *threadproc(void *data) {
          int thread_id = __sync_add_and_fetch(&thread_counter, 1);
          printf("Thread %d reporting for duty!\n", thread_id);
          return NULL;
      }
      

      如果你的平台支持这个,类似的选项:

      int thread_counter = 0;
      int __thread thread_id = 0;
      
      static void *threadproc(void *data) {
          thread_id = __sync_add_and_fetch(&thread_counter, 1);
          printf("Thread %d reporting for duty!\n", thread_id);
          return NULL;
      }
      

      这样做的好处是您不必在函数调用中传递 thread_id,但它不起作用,例如在 Mac 操作系统上。

      【讨论】:

        【解决方案7】:

        如果 pthread_t 只是一个数字;这将是最简单的。

        int get_tid(pthread_t tid)
        {
            assert_fatal(sizeof(int) >= sizeof(pthread_t));
        
            int * threadid = (int *) (void *) &tid;
            return *threadid;
        }
        

        【讨论】:

          【解决方案8】:

          查找表(pthread_t : int) 可能会在启动大量短期线程的程序中成为内存泄漏。

          创建pthread_t 字节的哈希(无论是结构、指针还是长整数或其他)可能是不需要查找表的可用解决方案。与任何散列一样,存在冲突的风险,但您可以调整散列的长度以满足您的要求。

          【讨论】:

          • 如果每个线程有多个可能的pthread_t 值来引用它怎么办?
          【解决方案9】:

          我知道,这个帖子很老了。阅读完以上所有帖子后,我想再添加一个想法来以一种简洁的方式处理这个问题: 如果您无论如何都进入映射业务(将 pthread_to 映射到 int),那么您还可以在可读性方面更进一步。使您的 pthread_create_wrapper 也接受一个字符串......线程的名称。 我学会了欣赏 Windows 和 Windows CE 上的“SetThreadName()”功能。 优点:您的 id 不仅是数字,而且您还可以看到,您的哪些线程有哪些用途。

          【讨论】:

            【解决方案10】:

            您可以尝试将其转换为无符号短整数,然后仅打印最后四个十六进制数字。生成的值可能对您的需求足够独特。

            【讨论】:

            • 如何确定这 2 个字节在所有线程 ID 中都是唯一的?
            • 我说它可能足够独特。您始终可以截断到最后 N 个数字,其中 N 是通过实验确定的。
            【解决方案11】:

            不用花哨,你可以把pthread_t当成一个不用强制转换的指针:

            printf("awesome worker thread id %p\n", awesome_worker_thread_id);
            

            输出:

            很棒的工作线程 id 0x6000485e0

            【讨论】:

              【解决方案12】:

              只是对第一篇文章的补充:使用用户定义的联合类型来存储pthread_t:

              union tid {
                  pthread_t pthread_id;
                  unsigned long converted_id;
              };
              

              当你想打印pthread_t时,创建一个tid并分配tid.pthread_id = ...,然后打印tid.converted_id

              【讨论】:

              • 无法保证这将为每个线程产生唯一的值。事实上,这就是为什么我们有像pthread_equal 这样的函数。
              【解决方案13】:

              正如 James 上面提到的,最好的方法是查看定义类型的标头。

              您可以在 pthreadtypes.h 中找到 pthread_t 的定义,该文件位于:

              /usr/include/bits/pthreadtypes.h

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-05-28
                相关资源
                最近更新 更多