Python
Threading
Thread ID
Programming
Python Tips

How to obtain a Thread id in Python?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Every thread in Python has a unique identifier that distinguishes it from other threads. You need this identifier for logging, debugging, and correlating work across concurrent operations. Python provides several ways to get a thread's ID depending on whether you need the Python-level identifier or the operating system's native thread ID. The two main approaches are threading.get_ident() for the Python thread ID and threading.get_native_id() (Python 3.8+) for the OS-level thread ID.

Python Thread ID with threading.get_ident()

python
1import threading
2
3def worker():
4    thread_id = threading.get_ident()
5    print(f"Worker thread ID: {thread_id}")
6
7t = threading.Thread(target=worker)
8t.start()
9t.join()
10
11# From the main thread
12print(f"Main thread ID: {threading.get_ident()}")

threading.get_ident() returns an integer that uniquely identifies the current thread among all active threads. This value is recycled when a thread exits and a new one is created, so it is only unique while the thread is alive.

OS-Level Thread ID with threading.get_native_id()

python
1import threading
2
3def worker():
4    native_id = threading.get_native_id()
5    print(f"OS thread ID: {native_id}")
6
7t = threading.Thread(target=worker)
8t.start()
9t.join()
10
11print(f"Main OS thread ID: {threading.get_native_id()}")

threading.get_native_id() was introduced in Python 3.8. It returns the kernel-assigned thread ID (e.g., the value from gettid() on Linux or GetCurrentThreadId() on Windows). This ID is useful when correlating Python threads with OS-level monitoring tools like htop, top, or strace.

Thread Object Attributes

python
1import threading
2
3def worker():
4    current = threading.current_thread()
5    print(f"Name: {current.name}")
6    print(f"Ident: {current.ident}")
7    print(f"Native ID: {current.native_id}")
8
9t = threading.Thread(target=worker, name="MyWorker")
10t.start()
11t.join()
12
13# Access from outside the thread
14print(f"Thread ident after start: {t.ident}")
15print(f"Thread native_id after start: {t.native_id}")

The threading.Thread object exposes .ident and .native_id (Python 3.8+) as attributes. These are None before the thread starts and get populated once it begins running.

Logging with Thread IDs

python
1import logging
2import threading
3
4logging.basicConfig(
5    format="%(asctime)s [%(threadName)s %(thread)d] %(message)s",
6    level=logging.INFO,
7)
8
9def worker():
10    logging.info("Processing task")
11
12threads = [threading.Thread(target=worker, name=f"Worker-{i}") for i in range(3)]
13for t in threads:
14    t.start()
15for t in threads:
16    t.join()

The logging module supports %(thread)d for the Python thread ID and %(threadName)s for the thread name. This makes it straightforward to trace which thread produced each log line without calling get_ident() manually.

Enumerating All Active Threads

python
1import threading
2import time
3
4def long_task():
5    time.sleep(2)
6
7threads = [threading.Thread(target=long_task, name=f"Task-{i}") for i in range(3)]
8for t in threads:
9    t.start()
10
11# List all active threads
12for t in threading.enumerate():
13    print(f"Thread: {t.name}, ID: {t.ident}, Alive: {t.is_alive()}")

threading.enumerate() returns a list of all active Thread objects. Each object has .ident, .name, and .native_id attributes that you can inspect for monitoring or debugging.

Mapping Thread IDs to concurrent.futures

python
1from concurrent.futures import ThreadPoolExecutor
2import threading
3
4def task(n):
5    tid = threading.get_ident()
6    return f"Task {n} ran on thread {tid}"
7
8with ThreadPoolExecutor(max_workers=3) as pool:
9    futures = [pool.submit(task, i) for i in range(6)]
10    for f in futures:
11        print(f.result())

When using ThreadPoolExecutor, the pool reuses threads. Calling threading.get_ident() inside the task function reveals which pool thread executed the task. Multiple tasks may share the same thread ID because the pool recycles threads.

Difference Between ident and native_id

Attributethreading.get_ident()threading.get_native_id()
Python versionAll versions3.8+
SourcePython runtimeOperating system kernel
RecycledYes, when thread exitsNo, unique per OS thread lifetime
Use caseIn-process identificationOS-level debugging (htop, strace)
PlatformAllLinux, macOS, Windows, FreeBSD

Common Pitfalls

  • Checking ident before start(): thread.ident is None until the thread actually starts running. Always call t.start() before reading t.ident.
  • Assuming ident is globally unique: Python recycles thread IDs after a thread exits. Two threads that never overlap in time can share the same ident. Use native_id if you need OS-level uniqueness.
  • Using get_native_id() on Python < 3.8: threading.get_native_id() raises AttributeError on older Python versions. Check sys.version_info >= (3, 8) or use threading.get_ident() as a fallback.
  • Confusing thread ID with process ID: threading.get_ident() identifies a thread within a process. For the process ID, use os.getpid(). In multiprocessing code, you need both to uniquely identify a worker.
  • GIL and true parallelism: Thread IDs prove threads exist, but the GIL prevents CPU-bound Python threads from running simultaneously. For CPU-bound parallelism, use multiprocessing and os.getpid() instead.

Summary

  • Use threading.get_ident() for the Python-level thread ID (available in all Python versions)
  • Use threading.get_native_id() for the OS kernel thread ID (Python 3.8+)
  • Access .ident and .native_id on Thread objects after calling .start()
  • Use %(thread)d in logging format strings to automatically include thread IDs
  • threading.enumerate() lists all active threads with their identifiers
  • Thread IDs from get_ident() are recycled — do not use them as permanent unique keys

Course illustration
Course illustration

All Rights Reserved.