C++
std::thread
thread naming
multithreading
programming tips

stdthread - naming your thread

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

When you are debugging a multithreaded application and your debugger shows "Thread 1," "Thread 2," and "Thread 14," figuring out which thread is doing what becomes a guessing game. Named threads solve this problem by giving each thread a human-readable label that appears in debuggers, profilers, and system monitoring tools. Unfortunately, the C++ standard library does not provide a built-in way to name threads, so you need to use platform-specific APIs to accomplish this.

Why Thread Naming Matters

Before diving into the how, consider the why. In a multithreaded application with a thread pool, worker threads, an I/O thread, and a rendering thread, a deadlock or performance bottleneck could involve any of them. Without names, you must cross-reference thread IDs with your code to determine each thread's purpose. Named threads make several tasks dramatically easier:

  • Debugging: Debuggers like GDB, LLDB, and Visual Studio display thread names in their thread list.
  • Profiling: Tools like perf, Instruments, and VTune label threads by name in timeline views.
  • Logging: You can retrieve the current thread's name and include it in log output for filtering.
  • System monitoring: Commands like top -H on Linux show thread names in the process listing.

Getting the Native Handle from std::thread

Since C++ does not expose a thread-naming API, you need to access the underlying platform thread handle. The std::thread class provides native_handle() for exactly this purpose.

cpp
1#include <thread>
2#include <iostream>
3
4void worker() {
5    std::cout << "Worker running\n";
6}
7
8int main() {
9    std::thread t(worker);
10
11    // Get the platform-specific thread handle
12    auto handle = t.native_handle();
13    // Use handle with platform APIs to set the name
14
15    t.join();
16    return 0;
17}

The type returned by native_handle() depends on the platform. On POSIX systems (Linux, macOS), it returns a pthread_t. On Windows, it returns a HANDLE. You must call native_handle() before calling join() or detach(), because after either of those calls, the handle becomes invalid.

Naming Threads on Linux

On Linux, the POSIX extension pthread_setname_np sets the name of a thread. The name is limited to 15 characters plus a null terminator (16 bytes total).

cpp
1#include <thread>
2#include <pthread.h>
3#include <cstring>
4
5void set_thread_name(std::thread& t, const char* name) {
6    auto handle = t.native_handle();
7    pthread_setname_np(handle, name);
8}
9
10void io_worker() {
11    // ... I/O processing logic
12}
13
14int main() {
15    std::thread io_thread(io_worker);
16    set_thread_name(io_thread, "io-worker");
17
18    io_thread.join();
19    return 0;
20}

A thread can also name itself by calling pthread_setname_np(pthread_self(), "name") from within its own execution context. This is useful when you want the thread function to set its own name immediately upon starting.

Naming Threads on macOS

macOS also uses pthread_setname_np, but with a crucial difference: a thread can only name itself. The function takes a single argument (just the name string) and applies it to the calling thread.

cpp
1#include <thread>
2#include <pthread.h>
3
4void render_worker() {
5    pthread_setname_np("render-worker");
6    // ... rendering logic
7}
8
9int main() {
10    std::thread render_thread(render_worker);
11    render_thread.join();
12    return 0;
13}

This means you cannot name a macOS thread from the parent. The naming call must happen inside the thread function itself.

Naming Threads on Windows

On Windows 10 version 1607 and later, the SetThreadDescription API provides a clean way to name threads.

cpp
1#include <thread>
2#include <windows.h>
3
4void set_thread_name(std::thread& t, const wchar_t* name) {
5    HANDLE handle = t.native_handle();
6    SetThreadDescription(handle, name);
7}
8
9void network_worker() {
10    // ... network processing logic
11}
12
13int main() {
14    std::thread net_thread(network_worker);
15    set_thread_name(net_thread, L"net-worker");
16
17    net_thread.join();
18    return 0;
19}

Unlike the older RaiseException-based trick that only worked with the Visual Studio debugger, SetThreadDescription is a proper OS API that persists the name across tools.

A Cross-Platform Helper Function

In real projects, you typically want a single helper that works on all platforms. You can achieve this with preprocessor directives.

cpp
1#include <thread>
2
3#ifdef __linux__
4  #include <pthread.h>
5#elif defined(__APPLE__)
6  #include <pthread.h>
7#elif defined(_WIN32)
8  #include <windows.h>
9#endif
10
11// Sets the name of the given thread (Linux/Windows)
12// On macOS, call set_current_thread_name() from within the thread
13void set_thread_name([[maybe_unused]] std::thread& t,
14                     [[maybe_unused]] const char* name) {
15#ifdef __linux__
16    pthread_setname_np(t.native_handle(), name);
17#elif defined(_WIN32)
18    // Convert char* to wchar_t* for Windows API
19    wchar_t wname[64];
20    mbstowcs(wname, name, 64);
21    SetThreadDescription(t.native_handle(), wname);
22#endif
23}
24
25// Sets the name of the calling thread (works on all platforms)
26void set_current_thread_name([[maybe_unused]] const char* name) {
27#ifdef __linux__
28    pthread_setname_np(pthread_self(), name);
29#elif defined(__APPLE__)
30    pthread_setname_np(name);
31#elif defined(_WIN32)
32    wchar_t wname[64];
33    mbstowcs(wname, name, 64);
34    SetThreadDescription(GetCurrentThread(), wname);
35#endif
36}

The set_current_thread_name function works uniformly across all three platforms because every platform supports a thread naming itself. This is the most portable approach.

Common Pitfalls

  • Exceeding the 15-character limit on Linux: pthread_setname_np silently truncates or returns an error if the name is too long. Keep names short and descriptive.
  • Calling native_handle() after join() or detach(): The handle is no longer valid after these calls, leading to undefined behavior.
  • Assuming macOS supports naming from the parent thread: On macOS, only the thread itself can set its name. Attempting to pass a handle results in a compilation error since the function signature differs.
  • Forgetting to link pthread on Linux: You must compile with -pthread or link with -lpthread when using pthread_setname_np.
  • Using SetThreadDescription on older Windows versions: This API is only available on Windows 10 1607 and later. Check for availability at runtime if you support older systems.

Summary

  • The C++ standard does not include a thread-naming API, so you must use platform-specific functions accessed through std::thread::native_handle().
  • On Linux, use pthread_setname_np(handle, name) with a 15-character limit.
  • On macOS, call pthread_setname_np(name) from within the thread itself, since only self-naming is supported.
  • On Windows 10+, use SetThreadDescription(handle, name) for a proper OS-level thread name.
  • Wrap these calls in a cross-platform helper to keep your application code clean and portable.
  • Named threads dramatically improve the debugging and profiling experience in multithreaded applications.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.