Threads are created via APIs like pthread_create() (POSIX) or Thread constructors (Java). Each thread has its own stack and registers but shares heap and code. Threads have lifecycles: created, ready, running, blocked, and terminated. Efficient thread management and careful lifecycle handling prevent resource leaks.
Write multi-threaded programs using pthreads or Java/C++ threading APIs to understand thread creation, joining, and lifecycle management.
From your study of user-level versus kernel-level thread models, you know that threads are lighter-weight execution contexts that share a process's address space. Thread creation and lifecycle management is where that theory becomes practice — it is the API-level skill of spawning threads, coordinating their work, and cleaning up after them without leaking resources or corrupting shared state.
Creating a thread means asking the OS or runtime to set up a new execution context: a separate stack, a set of registers (including its own program counter), and a thread ID. The heap, global variables, open file descriptors, and code segment remain shared with every other thread in the same process. In POSIX C, pthread_create() takes a function pointer and an argument, spawns a new thread that begins executing that function, and returns a thread ID. In Java, you instantiate a Thread with a Runnable and call start(). In Python, threading.Thread works similarly. The key point is that after creation, the new thread runs *concurrently* with the creating thread — their relative ordering is nondeterministic unless you add explicit synchronization.
A thread's lifecycle mirrors the process state model you may already know: a newly created thread moves to ready, gets scheduled to running, may block on I/O or a lock, and eventually terminates. Termination happens when the thread's function returns or when it explicitly calls an exit routine (pthread_exit in C). But termination alone does not free the thread's resources. By default, a POSIX thread is joinable, meaning another thread must call pthread_join() to collect its return value and release its stack and metadata — much like a parent process calling wait() on a child. If you forget to join, the terminated thread's resources leak, accumulating zombie-like state. The alternative is to make a thread detached (via pthread_detach or creating it with the detached attribute), which tells the system to automatically clean up when the thread exits. Detached threads cannot be joined, so you lose the ability to retrieve their return value.
The practical discipline of thread lifecycle management boils down to three rules. First, every joinable thread must eventually be joined by exactly one other thread. Second, resources shared between threads — variables, data structures, file handles — require synchronization (mutexes, condition variables) to prevent races, but that is a topic for concurrency control rather than lifecycle management. Third, be deliberate about how many threads you create. Each thread consumes stack memory (typically 1–8 MB by default), and spawning thousands of threads can exhaust memory or overwhelm the scheduler. Thread pools — pre-creating a fixed number of worker threads and feeding them tasks through a queue — are the standard pattern for managing this, and understanding the creation-and-lifecycle fundamentals is what makes thread pools intelligible when you encounter them.
No topics depend on this one yet.