Skip to content
Search

Multi Thread

lastmod 2026-06-10created 2015-04-0420 edits

A single application creating multiple processing units called threads to carry out multiple tasks in parallel. In other words, it refers to multitasking within an application. As with multitasking, the central processing unit (CPU) processing time is divided into very short slices and allocated to multiple threads in turn, so that multiple tasks appear to run at the same time.

One commonly confused concept is the difference between a process and a thread. A process is a single program unit, and a thread is the smallest unit of work inside a process.

When creating a thread, the handle and the ID are used as follows. A handle's scope is limited to the same process, while the ID is used for communication between different processes.

Thread

A thread is a unit of execution flow within a program, especially within a process. Generally a program has a single thread, but depending on the program environment, two or more threads can run at the same time. This mode of execution is called multithreading (multithread).

Windows vs Linux

Since the functions implementing processes and threads differ between Windows and Linux, they are summarized below.

Function

Windows

Linux

Create process

CreateProcess

fork
exec

Remove process

TerminateProcess

kill

Wait for object (synchronization)

WaitForSingleObject

waitpid
pthread_join
pthread_detach

Change and check thread priority

SetThreadPriority
GetThreadPriority

setpriority
getpriority

Get process ID

GetCurrentProcessID

getpid

Exit process

ExitProcess

exit

Create thread

CreateThread

pthread_create

Remove thread

ThreadExit

pthread_exit

Why threads?

Threads are used to overcome the inefficiency of fork, a problem of the process model. Here is a simple example. There is a naive web server. When requests come in from two or more clients, it duplicates itself to create a new process, and has that process handle one client. Here a fork system call occurs to copy the process, and to handle requests from many clients at once, that many forks occur, leaving the system unable to do other work because it is busy copying processes. This is very inefficient. Another reason is multiprocessing. A single process runs on a single processor (CPU). Since the system cannot automatically parallelize a process, running as a single process on a system capable of multiprocessing is very inefficient. Threads emerged to solve this inefficiency.

Differences between the process and thread models

  • The process model can be described as a heavy-weight model. When a process is copied, all of its data structures are recreated and copied, which is costly. The thread model, on the other hand, is a light-weight model. It shares most of the process's contents and only needs the thread's data structures, local variables, space for saving registers, a stack, a PC, and so on.
  • Traditional process data structure = Process Context + (data, code, stack), Process Context = Program Context + Kernel Context
  • Modern process data structure = Thread + (code, data) + Kernel context, Thread = Thread context + User stack
  • Each thread has its own logical control flow, i.e. a PC (Program Counter).
  • Each thread shares the same code, data, and kernel context.
  • Each thread has its own thread ID.
  • The logical structure is not a tree. Threads are derived from a process, but all threads are just children at the same level. Process duplication, on the other hand, forms a tree.
  • When the OS swaps out, it does so per process. Threads are swapped out together when their process is swapped out, so they are not swapped out separately.
  • A thread belongs to only one process. However, a process can have multiple threads.

Brief summary of similarities and differences between processes and threads

  • Similarities: Threads and processes are units of scheduling (context switch). Each also has a PC, its logical control flow, and they run concurrently.
  • Differences: Threads share code and data, but processes do not. Threads are also far cheaper in terms of creation, context switching, and termination.

Benefits of threads

They are economical because creation, termination, and context switching are cheap. Resource sharing enables improved communication. That is, unlike processes, threads can exchange information quickly without kernel intervention. Processes must go through IPC and the like, which is complex. Finally, they are very useful in multiprocessor environments. Each thread can run in parallel on a different processor.

Eight rules for designing multithreaded programs

This is a translation of the English text of chapter 4 of the book 'The Art of Concurrency'.

  1. Identify truly independent computations
  2. Implement concurrency at the highest level possible
  3. Plan early for scalability to take advantage of increasing numbers of cores
  4. Use thread-safe libraries wherever possible
  5. Use the right threading model
  6. Never assume a particular order of execution
  7. Use thread-local storage whenever possible or lock specific data
  8. Change the algorithm to improve concurrency

User Level, Kernel Level Threads

User Level Thread
Threads are managed by linking a library. The library is a miniature version of the kernel, used to control threads. Synchronization, creation, and scheduling can be done without the kernel's help. That is, it does not use kernel resources. However, it uses the same address space as the process. It stores context such as the user stack and registers separately. But since a single process manages the threads, it cannot support multiprocessors, and when a thread blocks, the entire process blocks.
Kernel Level Thread (threads are managed through system calls)
The kernel maintains context information for processes and threads. Scheduling can be done independently of the process, and the kernel's standard synchronization mechanisms can be used. A blocked thread does not block the process, and multiprocessors are supported. However, it is slower and heavier than user-level threads, because system calls are used for thread creation, scheduling, synchronization, and so on. Also, since thread information is stored in the kernel, the number of threads is limited by kernel resource constraints.

Threading Models

  • Many-to-one: Multiple threads run on a single kernel thread. A single kernel thread can be a process. That is, it is a model consisting only of user-level threads. It has the same pros and cons as user-level threads.
  • One-to-one: A model consisting only of kernel-level threads. It has the same pros and cons as kernel-level threads. (Win 2k, NT, OS/2, etc.)
  • Many-to-Many: A combination of many-to-one + one-to-one. Many user-level threads run on a single kernel-level thread, and multiple kernel-level threads can be created per process. Thread creation is completed in user space, and multiprocessors are supported. Also, a blocking system call does not block the entire process. (Solaris HP-UX, IRix, etc.)

Threading Issues

  • Fork Issue: When fork occurs inside a thread, should all threads be copied, or should only that thread be created in the new process?
  • Cancellation Issue :How should it be handled if a thread is cancelled while allocated memory remains or while data shared with other threads is being updated?
  • Signal handling Issue : Which thread handles a signal when it occurs? That is, should the signal be sent to all threads or only to a specific thread? For reference, Solaris has all threads handle signals.
  • Thread polling Issue : When a process starts, it creates as many threads as possible and keeps them in a pool, then uses them when work is needed.
  • Thread Interface Issue : Vender-dependent. That is, it differs by OS. However, when the POSIX standard is supported, as with pThreads, threads can be managed with the same interface.

Terminate Thread

Ways to terminate a thread are described in order of preference.

  1. Return from the thread function. (most preferable)
  2. Call a thread exit function inside the thread function (in Win32, call ExitThread()).
  3. Remote termination from the same process or another process (in Win32, call TerminateThread()).
  4. Terminate the process containing the thread.

Win32 thread termination issues

Why call CloseHandle:

스레드 핸들을 사용하지 않을 경우 보통 스레드생성 직후 CloseHandle()을 호출한다.
스레드 생성 직후 CloseHandle()을 호출한다고 해서 스레드를 사용하지 못하는 것은 아니다.
윈도우는 핸들을 커널 오브젝트로서 레퍼런스 카운트로 관리되고 있다. (마치 DLL처럼)
스레드의 경우 레퍼런스 카운트는 생성되며 +1, 함수가 호출되며 +1된다.
(총 +2) 동일하게 스레드 함수가 리턴하게 되면 -1, CloseHandle()을 사용해도 -1된다.
최종적으로 레퍼런스 카운트가 0이 될 경우 스레드는 자동으로 없어진다.
따라서 이 핸들에 대해서 조정할 필요가 없을 경우, 생성직후 바로 CloseHandle()을 호출해서 레퍼런스 값을 줄여주는 것이 좋다.

TerminateThread caveats:

TerminateThread()는 비동기 함수이다. 따라서 이 함수를 호출하여 스레드를 종료할 경우 함수가 반환되기 이전에 해당 스레드가 종료되었음을 보장할 수 없다.
정확한 스레드 종료 시점을 알고싶다면 WaitForSingleObject()를 통하여 스레드의 종료를 기다린다.
스레드는 자신이 종료될것이라는 사실을 전달받지 못하기 때문에 적절한 정리작업을 수행하지 못한다.
시스템은 종료된 스레드를 소유하고 있던 프로세스가 살아있는 동안 그 스레드가 사용했던 스레드 스택을 정리하지 않는다.

How to suspend/resume Win32 threads

Suspending and resuming threads comes with the following caveats.

  • Suspending/resuming a thread increments/decrements the suspend count variable.
  • Calling SuspendThread increments the suspend count variable by 1,
  • Calling ResumeThread decrements the suspend count variable by 1.
  • A thread runs only when its suspend count is 0.

Thread priority

Describes issues related to thread execution priority and how to handle them.

SetThreadPriority, GetThreadPriority
Gets and sets Win32 thread priority.
SetPriorityClass, GetPriorityClass
Gets and sets Win32 process priority.

Thread structure by OS

  • Solaris: A many-to-many model. Multiple user-level threads run on multiple LWPs (Light Weight Processes). An LWP is a kernel-level thread. A user-level thread is bound to one LWP. User-level threads are scheduled through the thread library without kernel intervention. Each process has at least one LWP. The kernel maintains the LWP data structures.
  • Window 2k: A one-to-one model.
  • Linux Thread: Supports multithreading. However, it does not support efficient kernel-level multithreading. Instead, it provides something similar to LWPs. Its distinguishing feature is that it does not distinguish between processes and threads. It uses only a single Task_struct. LWPs are created through the clone() system call, which is similar to fork. Instead of making a copy of the process, it creates a separate process that shares the parent task's address space.

Thread model

Pros and cons of kernel-level threads

  • Pros: Because the kernel provides them directly, they offer safety and a wide range of functionality.
  • Cons: Because the functionality is provided by the kernel, switches from user mode to kernel mode happen frequently, which leads to degraded performance.

Pros and cons of user-level threads

  • Pros: Since the kernel does not know threads exist, no switch from user mode to kernel mode is needed. So performance is good.
  • Cons: The operating system only knows that processes exist, not threads. So you must program carefully so that thread problems do not arise within a process, which makes programming harder and results harder to predict than with kernel-level threads.

See also

Favorite site

References


  1. Windows_process_and_thread.zip ↩

  2. C_-_reentrant_function_and_thread-safe_function.pdf ↩

  3. C++11_threads_-affinity_and_hyperthreading-_Eli_Bendersky's_website.pdf ↩

  4. Top_20_Cpp_multithreading_mistakes_and_how_to_avoid_them_-_A_CODER'S_JOURNEY.pdf ↩

  5. C11_vs_Cpp11_vs_POSIX_Thread_API_comp_-_jacking75.pdf ↩

author Zer0 sitemap