如何打印pthread_t

65
搜索过,但没有找到令人满意的答案。
我知道没有一种便携式的方法来打印pthread_t。
您是如何在应用程序中实现的?
更新:
实际上,我不需要pthread_t,而只是一些小的数字ID,在调试消息中标识不同的线程。
在我的系统(64位RHEL 5.3)中,它被定义为无符号长整型,因此它是一个大数字,仅仅打印它会占据调试行中的有价值的位置。 gdb如何分配短TID?
13个回答

40

不管 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<,因为std::map需要对元素进行排序:

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

+1 的可移植性。但是这种解决方案产生的 pthread_t 表示甚至比它的数字“非便携”解决方案更长。 - dimba
gdb 分配短数字。我希望能有类似的解决方案。 - dimba
1
R. Pate:不,它没有。请注意,在fprintf行中对ptc指针进行了解引用。 - caf
在将类型转换为unsigned char*之前,将其强制转换为void*的目的是什么?为什么第一个转换不是多余的? - fonini

28

GDB在Linux系统中使用线程ID(也称内核PID或LWP)表示短数字。尝试以下命令:

  #include <syscall.h>
  ...

    printf("tid = %d\n", syscall(SYS_gettid));

这会返回LWP。也许我错了,但是pthread可以在运行时绑定到不同的LWP,因此不能唯一地标识不同的pthread。 - dimba
6
在Linux上,pthread_t和LWP之间存在一对一的映射关系。在其生命周期内,同一个线程不会报告不同的LWP。 - Employed Russian
在Linux上称其为LWP是我个人认为是错误的。因为在Linux中没有“轻量级”和“重量级”进程之分。只有任务,可调度的实体,它们可以共享各种资源。这就是这个特定于Linux的系统调用返回的“任务”标识符。 - Jan Hudec
2
谢谢!使用%lu可以避免在gcc -Wall下出现警告。 - Pramod

20
在这种情况下,它取决于操作系统,因为POSIX标准不再要求 pthread_t 是算术类型

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

您需要查看您的 sys/types.h 标头并查看如何实现 pthread_t;然后可以按照您认为合适的方式打印它。由于没有便携式的方法来执行此操作,并且您没有说明使用的操作系统,因此没有更多可说的。 编辑:回答您的新问题,每次启动新线程时,GDB都会分配自己的线程ID

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

如果您正在考虑在每个线程内打印唯一编号,则最清晰的选择可能是在启动线程时告诉每个线程要使用的编号。

一个线程本地存储变量也可以工作。你也可以推迟分配数字,直到需要为给定的线程,但这涉及到特定于您的程序的权衡。 - Roger Pate
詹姆斯:「最干净的选项可能是在启动每个线程时告诉它要使用哪个数字。」 - 我刚刚查看了 pthread_create,但没有什么引起我的注意。如何做到这一点? - jww
pthread_create 的第四个参数允许您将任意数据传递给线程过程。 - James McNellis

9

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

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

1. 打印POSIX ID(pthread_t)

您可以将pthread_t简单地视为字节数组,并为每个字节打印十六进制数字。因此,您不受某些固定大小类型的限制。唯一的问题是字节顺序。您可能希望您打印的字节顺序与简单的“int”打印的顺序相同。以下是小端示例,仅需反转顺序(在#define下?)以适应大端:

#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 查找表。我也在考虑这种方法。虽然我无法控制所有创建的线程,但它们都使用相同的函数在日志记录器中打印消息。因此,我可以在记录时构建查找表。 - dimba
pthread_t不是无符号整数时,printf("%08x\n", id);是未定义的行为。为了避免这种情况,请使用printf("%jx\n", (uintmax_t) id);,只要pthread_t是数字即可。 - chux - Reinstate Monica

4
在Centos 5.4 x86_64上,pthread_t是一个无符号长整型的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";
}

"%li" 是用于 有符号 类型的。使用 printf("%lu\n", (unsigned long int) x); 更好。为什么不直接转换为可用的最宽类型以最大化可移植性?使用 printf("%ju\n", (uintmax_t) x); - chux - Reinstate Monica

4
如果pthread_t只是一个数字,那么这将是最简单的。
int get_tid(pthread_t tid)
{
    assert_fatal(sizeof(int) >= sizeof(pthread_t));

    int * threadid = (int *) (void *) &tid;
    return *threadid;
}

3
你可以像这样做:

你可以这样做:

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 OS上无法使用。


2

我知道这个帖子非常古老。在阅读了以上所有帖子之后,我想要提出一个更好的方法来处理这个问题: 如果你已经涉足映射业务(将pthread_to映射到一个整数),那么你可以更进一步地提高可读性。使你的pthread_create_wrapper函数接受一个字符串,即线程的名称。 我学会了在Windows和Windows CE上使用"SetThreadName()"功能。 优点:你的id不仅仅是数字,而且你还可以看到你的每个线程的作用。


2

如果程序启动了大量短暂的线程,查找表 (pthread_t : int) 可能会导致内存泄漏。

pthread_t 的字节创建哈希(无论是结构体、指针、长整型还是其他类型)可能是一种可行的解决方案,不需要使用查找表。与任何哈希一样,存在碰撞的风险,但您可以调整哈希的长度以适应您的要求。


如果每个线程有多个可能引用它的 pthread_t 值,那该怎么办? - David Schwartz

1
你可以尝试将它转换为无符号短整型,然后只打印最后四个十六进制数字。得到的值可能足够唯一,适合你的需求。

我怎样才能确保这2个字节在所有线程ID中是唯一的? - dimba
我说它可能足够独特。您始终可以截断到最后的N位数字,其中N是通过实验确定的。 - David R Tribble

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接