Thread IDs

Thread IDs
Part 5 of 9  |  pthread_self() · pthread_equal() · TID vs PID
Level
Beginner
Functions
pthread_self() / pthread_equal()
Book
TLPI – Ch 29.5

Just as every process has a unique process ID (PID), every thread within a process has a unique thread ID (TID). Thread IDs are the primary handle you use to refer to a specific thread in Pthreads function calls. This part explains how to get a thread’s own ID, how to compare IDs, and an important distinction between POSIX thread IDs and the kernel-level thread IDs you may encounter in tools like top or /proc.

Key Terms

pthread_t pthread_self() pthread_equal() Opaque Type gettid() NPTL Thread Identification

1. Why Thread IDs Are Necessary

Thread IDs serve two main purposes in Pthreads programming:

1. Identifying threads in API calls

Many Pthreads functions require a thread ID to know which thread to act on. Examples:

  • pthread_join(tid, ...) — wait for a thread
  • pthread_detach(tid) — detach a thread
  • pthread_cancel(tid) — cancel a thread
  • pthread_kill(tid, sig) — send a signal
2. Tagging data structures

In some applications, you want to record which thread created or “owns” a particular heap-allocated structure. Storing the thread ID alongside the data makes this possible. Another thread can later check which thread originally created it.

2. pthread_self() — Getting Your Own Thread ID

#include <pthread.h>

pthread_t pthread_self(void);
/* Returns: the thread ID of the calling thread */

pthread_self() returns the thread ID of the thread that calls it. This is how a thread discovers its own identity. It always succeeds — there is no error return.

Important: The standard does not guarantee that a new thread’s ID is written into the pthread_t buffer (the first argument of pthread_create()) before the new thread starts executing. Therefore, if a thread needs to know its own ID at startup, it must call pthread_self() — it cannot rely on reading the buffer that was passed to pthread_create().

Typical use: self-detach

/* A thread detaching itself */
pthread_detach(pthread_self());

3. pthread_equal() — Comparing Thread IDs

#include <pthread.h>

int pthread_equal(pthread_t t1, pthread_t t2);
/* Returns: non-zero (true) if t1 and t2 are the same thread, 0 otherwise */

Because pthread_t is an opaque type, you cannot compare two thread IDs using the C == operator — that comparison may not be portable (on some implementations, pthread_t is a struct or pointer). Always use pthread_equal().

/* Check if tid is the same as the current thread */
if (pthread_equal(tid, pthread_self())) {
    printf("That's me!\n");
}

4. POSIX Thread IDs vs Linux Kernel Thread IDs

There are actually two different types of thread IDs in Linux, and they are completely separate from each other. This confuses many beginners.

Property POSIX Thread ID (pthread_t) Kernel Thread ID (gettid)
How to get it pthread_self() gettid() (Linux-specific)
Type pthread_t (opaque) pid_t (integer)
Assigned by Threading library (userspace) Linux kernel
Visible in /proc? No Yes (in /proc/PID/task/TID)
Portable? Yes (POSIX) No (Linux-specific)
Unique across processes? Linux: yes. POSIX: not guaranteed Yes (system-wide)
Note on NPTL: In the Linux NPTL implementation, pthread_t is actually a pointer cast to unsigned long. So on Linux you can sometimes print it with printf("%lu", (unsigned long)tid), but this is NOT portable and SUSv3 does not guarantee pthread_t is a scalar.

5. Thread ID Reuse

An important subtlety: after a thread has terminated and been joined (or after a detached thread terminates), the OS is allowed to reuse its thread ID for a newly created thread.

This means you should never store a thread ID and try to use it after the thread has terminated and been joined — you might inadvertently operate on a completely different new thread that happens to have the same ID.

Rule: Only use a pthread_t ID while the thread is known to still be alive (not yet joined/detached-and-finished).

6. Code Example: pthread_self() and pthread_equal()

/* compile: gcc -pthread -o thread_id thread_id.c */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

static pthread_t main_tid;  /* stored at process start */

static void *worker(void *arg)
{
    pthread_t self = pthread_self();   /* get own ID */

    /* Check if we are the same thread as main */
    if (pthread_equal(self, main_tid))
        printf("Worker: I am the main thread (unexpected!)\n");
    else
        printf("Worker: I am NOT the main thread (correct)\n");

    /* On Linux you can print tid as unsigned long for debugging */
    printf("Worker: pthread_t value = %lu\n", (unsigned long) self);
    return NULL;
}

int main(void)
{
    pthread_t tid;

    main_tid = pthread_self();   /* save main thread's ID */
    printf("Main:   pthread_t = %lu\n", (unsigned long) main_tid);

    pthread_create(&tid, NULL, worker, NULL);
    pthread_join(tid, NULL);

    /* Verify equality check works */
    if (pthread_equal(tid, main_tid))
        printf("Main: tid == main_tid (WRONG)\n");
    else
        printf("Main: tid != main_tid (CORRECT)\n");

    return 0;
}

7. Code Example: Tagging Data with Thread ID

Here is a practical pattern: a work item struct that records which thread created it, so it can later be verified.

/* compile: gcc -pthread -o thread_tag thread_tag.c */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

struct WorkItem {
    int       data;
    pthread_t creator;   /* which thread created this item */
};

static void *producer(void *arg)
{
    struct WorkItem *item = malloc(sizeof(struct WorkItem));
    item->data    = 999;
    item->creator = pthread_self();   /* tag with our own ID */

    printf("Producer: created item with data=%d\n", item->data);
    return item;   /* pass ownership to joiner */
}

int main(void)
{
    pthread_t tid;
    void     *ret;
    struct WorkItem *item;

    pthread_create(&tid, NULL, producer, NULL);
    pthread_join(tid, &ret);

    item = (struct WorkItem *) ret;

    /* Verify: the creator should match the thread we spawned */
    if (pthread_equal(item->creator, tid))
        printf("Main: item was created by the expected thread ✓\n");
    else
        printf("Main: creator mismatch!\n");

    printf("Main: item data = %d\n", item->data);
    free(item);
    return 0;
}

8. Interview Questions

Q1. How does a thread find out its own ID?
By calling pthread_self(), which returns the thread ID of the calling thread. It cannot rely on reading the pthread_t buffer filled by pthread_create(), because the new thread may start executing before the buffer is written.
Q2. Why can’t you compare two pthread_t values with == ?
pthread_t is an opaque type. Its internal representation is implementation-defined — it could be a struct, pointer, or integer. The == operator may not give correct results for structs. Use pthread_equal() which is portable and correct on all implementations.
Q3. What is the difference between a POSIX thread ID and a Linux kernel thread ID?
POSIX thread IDs (pthread_t, obtained via pthread_self()) are assigned by the threading library and are opaque. Linux kernel thread IDs are integers assigned by the kernel (obtained via gettid()) and are visible in /proc/PID/task/TID. They are completely independent. Portable applications should not use kernel TIDs.
Q4. Can thread IDs be reused? What are the implications?
Yes. After a thread has terminated and been joined (or a detached thread terminates), the implementation may reuse its ID for a new thread. The implication: never use a thread ID after the thread has terminated — you might accidentally reference a new, unrelated thread with the same ID.
Q5. On Linux, what underlying type is pthread_t (in NPTL)?
In NPTL (Native POSIX Threads Library), pthread_t is defined as unsigned long, and is actually a pointer cast to that type. However, this is an implementation detail — portable code must treat it as opaque and never assume it is a scalar.

Next: Part 6 — Joining with a Terminated Thread

How to wait for a thread to finish, retrieve its exit value, and why un-joined threads become “zombie threads”.

Leave a Reply

Your email address will not be published. Required fields are marked *