Thread Attributes

Thread Attributes
Part 8 of 9  |  pthread_attr_t · Stack · Scheduling · Detach State
Level
Intermediate
Object
pthread_attr_t
Book
TLPI – Ch 29.8

Every time you call pthread_create() with NULL as the second argument you are accepting a set of default thread behaviours. For most programs that is perfectly fine. But sometimes you need more control — a larger stack, a specific scheduling priority, or a thread that starts in the detached state. The pthread_attr_t object is the mechanism for controlling all of these at creation time.

Key Terms

pthread_attr_t pthread_attr_init() pthread_attr_destroy() pthread_attr_setdetachstate() pthread_attr_setstacksize() pthread_attr_setschedpolicy() PTHREAD_CREATE_DETACHED PTHREAD_CREATE_JOINABLE

1. What Is pthread_attr_t?

pthread_attr_t is an opaque object (like pthread_t) that bundles together all the configurable properties of a thread before it is created. You cannot inspect its internals directly — you use dedicated API functions to set and get each attribute.

The typical workflow is:

pthread_attr_init()

Initialise with defaults

Set attributes

setdetachstate, setstacksize…

pthread_create()

Pass &attr as 2nd arg

pthread_attr_destroy()

Free resources (if any)

Step ④ is important even though pthread_attr_destroy() may be a no-op on some implementations. On others it frees memory. Always call it for correctness and portability. The attribute object can be destroyed immediately after pthread_create() — modifying it afterwards has no effect on already-created threads.

2. Common Thread Attributes

Attribute Setter Function Default Value Description
Detach state pthread_attr_setdetachstate() PTHREAD_CREATE_JOINABLE Whether thread is joinable or auto-cleans up
Stack size pthread_attr_setstacksize() System default (often 8 MB) Size of the thread’s private stack
Stack address pthread_attr_setstackaddr() System-chosen Override where the stack lives in memory
Guard size pthread_attr_setguardsize() System page size Guard page to detect stack overflow
Scheduling policy pthread_attr_setschedpolicy() SCHED_OTHER SCHED_FIFO, SCHED_RR, or SCHED_OTHER
Scheduling priority pthread_attr_setschedparam() Inherited from creator Realtime priority level (for SCHED_FIFO/RR)
Scope pthread_attr_setscope() PTHREAD_SCOPE_SYSTEM System-scope (competes with all processes) or process-scope

3. Stack Size — When and Why to Change It

The default stack size on Linux is typically 8 MB per thread. For most threads this is more than enough. However, there are two scenarios where you would want to change it:

Reduce stack size

If you are creating many hundreds or thousands of threads, each holding 8 MB of virtual address space can exhaust the process’s address space (3 GB on 32-bit; much larger on 64-bit). Reducing the stack to e.g. 64 KB per thread lets you run far more concurrent threads. Embedded systems also often have tight memory constraints.

Increase stack size

If a thread uses deep recursion or declares very large local arrays/structs on the stack, it might overflow the default 8 MB stack. Increasing the stack size prevents the stack overflow (which usually results in a SIGSEGV signal).

Minimum stack size: POSIX defines a constant PTHREAD_STACK_MIN (from <limits.h>) which is the minimum allowed stack size. Never set the stack smaller than this.

4. Scheduling Attributes — Realtime Threads

By default all threads use the SCHED_OTHER (normal time-sharing) policy. For realtime or high-priority work you can set SCHED_FIFO (first-in first-out) or SCHED_RR (round-robin).

⚠️ Requires root / CAP_SYS_NICE capability

Setting realtime scheduling policies on threads requires elevated privileges. Attempting it as a normal user will fail with EPERM. These are covered in detail in Sections 35.2–35.3 of TLPI.

5. Code Example: Custom Stack Size

This example creates a thread with a small stack (256 KB instead of the default 8 MB). Useful when spawning many threads in a memory-constrained environment.

/* compile: gcc -pthread -o custom_stack custom_stack.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>    /* PTHREAD_STACK_MIN */
#include <pthread.h>

#define STACK_SIZE  (256 * 1024)   /* 256 KB */

static void *worker(void *arg)
{
    printf("Thread running with custom stack size\n");

    /* Demonstrate: allocating a local array of 64 KB — fits in 256 KB stack */
    char buf[64 * 1024];
    buf[0] = 'A';
    buf[(64*1024) - 1] = 'Z';
    printf("Local buffer: first=%c last=%c\n", buf[0], buf[(64*1024)-1]);

    return NULL;
}

int main(void)
{
    pthread_t      tid;
    pthread_attr_t attr;
    int            s;

    /* Verify our desired size is at least the minimum */
    if (STACK_SIZE < PTHREAD_STACK_MIN) {
        fprintf(stderr, "Stack size too small! Minimum is %d bytes.\n",
                (int)PTHREAD_STACK_MIN);
        exit(1);
    }

    s = pthread_attr_init(&attr);
    if (s != 0) { fprintf(stderr, "attr_init: %s\n", strerror(s)); exit(1); }

    /* Set custom stack size */
    s = pthread_attr_setstacksize(&attr, STACK_SIZE);
    if (s != 0) { fprintf(stderr, "setstacksize: %s\n", strerror(s)); exit(1); }

    /* Verify the size was set correctly */
    size_t actual_size;
    pthread_attr_getstacksize(&attr, &actual_size);
    printf("Requested stack: %d bytes, actual: %zu bytes\n",
           STACK_SIZE, actual_size);

    s = pthread_create(&tid, &attr, worker, NULL);
    if (s != 0) { fprintf(stderr, "create: %s\n", strerror(s)); exit(1); }

    pthread_attr_destroy(&attr);   /* safe to destroy after create */

    pthread_join(tid, NULL);
    printf("Done.\n");
    return 0;
}

6. Code Example: Combining Multiple Attributes

You can set several attributes on a single pthread_attr_t object before calling pthread_create(). This example sets both a custom stack size and the detached state.

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

#define STACK_SIZE  (512 * 1024)   /* 512 KB */

static void *background_task(void *arg)
{
    int task_id = (int)(intptr_t) arg;
    printf("BG Task %d: running (detached, 512KB stack)\n", task_id);
    /* simulate some work */
    for (volatile int i = 0; i < 100000000; i++);
    printf("BG Task %d: complete\n", task_id);
    return NULL;
}

int main(void)
{
    pthread_t      tids[3];
    pthread_attr_t attr;
    int            s;

    s = pthread_attr_init(&attr);
    if (s) { fprintf(stderr, "attr_init: %s\n", strerror(s)); exit(1); }

    /* Attribute 1: detached — no join needed */
    s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (s) { fprintf(stderr, "setdetachstate: %s\n", strerror(s)); exit(1); }

    /* Attribute 2: custom stack size */
    s = pthread_attr_setstacksize(&attr, STACK_SIZE);
    if (s) { fprintf(stderr, "setstacksize: %s\n", strerror(s)); exit(1); }

    /* Create 3 threads that all share the same attribute set */
    for (int i = 0; i < 3; i++) {
        s = pthread_create(&tids[i], &attr, background_task,
                           (void *)(intptr_t)(i + 1));
        if (s) { fprintf(stderr, "create %d: %s\n", i, strerror(s)); exit(1); }
        printf("Main: launched background task %d\n", i + 1);
    }

    /* Attribute object no longer needed after all creates */
    pthread_attr_destroy(&attr);

    printf("Main: all tasks launched. Exiting via pthread_exit.\n");
    pthread_exit(NULL);   /* let detached threads finish */
    return 0;
}

All three threads share the same attribute configuration (detached + 512 KB stack). The attribute object is destroyed after all three pthread_create() calls — this is safe because the attributes are copied into each thread at creation.

7. Reading Attribute Values (Getters)

Every pthread_attr_set*() function has a corresponding pthread_attr_get*() function. These are useful for verifying that attributes were set correctly, or for reading the default values before modifying them.

pthread_attr_t attr;
pthread_attr_init(&attr);

/* Read detach state */
int detach_state;
pthread_attr_getdetachstate(&attr, &detach_state);
printf("Detach state: %s\n",
       detach_state == PTHREAD_CREATE_JOINABLE ? "JOINABLE" : "DETACHED");

/* Read stack size */
size_t stack_size;
pthread_attr_getstacksize(&attr, &stack_size);
printf("Default stack size: %zu bytes (%zu MB)\n",
       stack_size, stack_size / (1024 * 1024));

pthread_attr_destroy(&attr);

8. Interview Questions

Q1. What is pthread_attr_t and what is it used for?
pthread_attr_t is an opaque object that bundles thread creation attributes such as stack size, stack address, detach state, scheduling policy, and priority. It is initialised with pthread_attr_init(), configured with setter functions, passed to pthread_create(), and then destroyed with pthread_attr_destroy().
Q2. Why would you want to reduce a thread’s stack size?
When creating many hundreds or thousands of threads, the default 8 MB stack per thread can exhaust the process’s virtual address space (especially on 32-bit systems). Reducing stack size allows more concurrent threads. It is also important on embedded systems with limited RAM.
Q3. Is it safe to destroy the pthread_attr_t object immediately after pthread_create()?
Yes. The attribute values are copied into the thread’s internal structures at creation time. Modifying or destroying the pthread_attr_t object after pthread_create() has no effect on the already created thread.
Q4. What is PTHREAD_STACK_MIN?
PTHREAD_STACK_MIN (defined in <limits.h>) is the minimum stack size allowed by the POSIX standard for a thread. Attempting to set a stack size smaller than this value will fail with EINVAL. Always check your desired size against this minimum.
Q5. Can you reuse the same pthread_attr_t to create multiple threads?
Yes. You can pass the same pthread_attr_t object to multiple pthread_create() calls. Each thread gets its own independent copy of the attribute values. Destroy the object only after the last pthread_create() call.
Q6. What happens if a thread overflows its stack?
A stack overflow typically triggers a SIGSEGV (segmentation fault) because the thread writes beyond its stack into a guard page or unmapped memory. This usually crashes the entire process. Prevention: either increase the thread’s stack size via pthread_attr_setstacksize(), or reduce recursive depth / large local allocations. Use heap (malloc()) for very large data instead of local arrays.

Next: Part 9 — Threads vs Processes & Summary

When should you choose threads over processes? The advantages, disadvantages, and a full chapter summary with exercises.

Leave a Reply

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