Threads in Operating System
Threads in Operating System
Processes: Review
Multiprogramming versus multiprocessing Kernel data structure: process control block (PCB) Each process has an address space
Contains code, global and local variables..
Performance metrics: throughput, CPU utilization, turnaround time, response time, fairness
Process Behavior
Processes: alternate between CPU and I/O CPU bursts
Most bursts are short, a few are very long (high variance) Modeled using hyperexponential behavior If X is an exponential r.v. Pr [ X <= x] = 1 e-mx E[X] = 1/m If X is a hyperexponential r.v. Pr [X <= x] = 1 p e-m1x -(1-p) e-m2x E[X] = p/ m1 + (1-p)/ m2
Process Scheduling
Priority queues: multiples queues, each with a different priority
Use strict priority scheduling Example: page swapper, kernel tasks, real-time tasks, user tasks
Introduction
A thread is a path of execution through a programs code, plus a set of resources (stack, register state, etc) assigned by the operating system.
Threads run in the context of a process. Each process has at least one thread. A thread represents a path of execution that has its own call stack and CPU state. Threads are confined to context of the process that created them.
A thread executes code and manipulates data within its processs address space. If two or more threads run in the context of a single process they share a common address space. They can execute the same code and manipulate the same data. Threads sharing a common process can share kernel object handles because the handles belong to the process, not individual threads.
Threads
Each thread has its own stack, PC, registers
Share address space, files,
Threads have full access to address space (easy sharing) Threads can execute in parallel on multiprocessors
Starting a Process
Every time a process starts, the system creates a primary thread.
The thread begins execution with the C/C++ run-time librarys startup code. The startup code calls your main or WinMain and execution continues until the main function returns and the C/C++ library code calls ExitProcess.
Scheduling Threads
Windows 2000, NT and Win98 are preemptive multitasking systems. Each task is scheduled to run for some brief time period before another task is given control of CPU. Threads are the basic unit of scheduling on current Win32 platforms. A thread may be in one of three possible states:
running blocked or suspended, using virtually no CPU cycles ready to run, using virtually no CPU cycles
Blocked threads become ready to run when an event or resource they wait on becomes available. Suspended threads become ready to run when their sleep interval has expired or suspend count is zero.
Threads (Benefits)
Responsiveness
Program can continue even if part of it is blocked or is performing lengthy operation. Web browser can still allow user interaction in one thread during an image load thread
Resource sharing
Several different threads of activity within the same address space -share the code and data section
Economy
Costly Memory and resource allocation for process Comparatively time consuming to create and manage processes than threads Solaris: creating a process 30 times slower Context switching 5 times slower
Your program may need to respond to high priority events. In this case, the design is easier to implement if you assign that event handler to a high priority thread. Take advantage of multiple processors available for a computation. Avoid low CPU activity when a thread is blocked waiting for response from a slow device or human by allowing other threads to continue.
More Benefits
Improve robustness by isolating critical subsystems on their own threads of control. For simulations dealing with several interacting objects the program may be easier to design by assigning one thread to each object.
Starvation
a high priority thread dominates CPU resources, preventing lower priority threads from running often enough or at all.
Deadlock
two or more tasks each own resources needed by the other preventing either one from running so neither ever completes and never releases its resource
Why Threads?
Single threaded process: blocking system calls, no parallelism Finite-state machine [event-based]: non-blocking with parallelism Multi-threaded process: blocking system calls with parallelism Threads retain the idea of sequential processes with blocking system calls, and yet achieve parallelism Software engineering perspective
Applications are easier to structure as a collection of threads Each thread performs several [mostly independent] tasks
0x00000000
PC
code (text)
Solaris 2 Threads
Solaris Process
Thread Management
Creation and deletion of threads
Static versus dynamic
Critical sections
Synchronization primitives: blocking, spin-lock (busy-wait) Condition variables
One-to-One
Many-to-Many Model
Allows many user level threads to be mapped to many kernel threads Allows the operating system to create a sufficient number of kernel threads Solaris prior to version 9 Windows NT/2000 with the Thread/Fiber package
Many-to-Many Model
Two-level Model
Similar to M:M, except it allows a specific thread to be bound to one kernel thread Examples
IRIX HP-UX Tru64 UNIX Solaris 8 and earlier
Two-level Model
Many-to-One
Many user-level threads mapped to single kernel thread Examples:
Solaris Green Threads GNU Portable Threads
Many-to-One
Many user-level threads mapped to single kernel thread. Used on systems that do not support kernel threads.
Many-to-One Model
One-to-One
Each user-level thread maps to kernel thread Examples
Windows NT/XP/2000 Linux Solaris 9 and later
One-to-one Model
User Threads
Thread management done by user-level threads library Examples - POSIX Pthreads - Mach C-threads - Solaris threads
Kernel Threads
Supported by the Kernel Examples - Windows 95/98/NT/2000 - Solaris - Tru64 UNIX - BeOS - Linux
Ease of scheduling Flexibility: many parallel programming models and schedulers Process blocking a potential problem
User-level Threads
Threads managed by a threads library
Kernel is unaware of presence of threads
Advantages:
No kernel modifications needed to support threads Efficient: creation/deletion/switches dont need system calls Flexibility in scheduling: library can use different scheduling algorithms, can be application dependent
Disadvantages
Need to avoid blocking system calls [all threads block] Threads compete for one another Does not take advantage of multiprocessors [no real parallelism]
User-level threads
Kernel Threads
Supported by the Kernel
OS maintains data structures for thread state and does all of the work of thread implementation.
Examples
Solaris Tru64 UNIX Mac OS X Windows 2000/XP/Vista Linux version 2.6
Negatives
System calls (high overhead) for operations Additional OS data space for each thread
Kernel-level threads
Kernel aware of the presence of threads
Better scheduling decisions, more expensive Better for multiprocessors, more overheads for uniprocessors
Light-weight Processes
Several LWPs per heavy-weight process User-level threads package
Create/destroy threads and synchronization primitives
Multithreaded applications create multiple threads, assign threads to LWPs (one-one, many-one, many-many) Each LWP, when scheduled, searches for a runnable thread [two-level scheduling]
Shared thread table: no kernel support needed
When a LWP thread block on system call, switch to kernel mode and OS context switches to another LWP
LWP Example
Thread Packages
Posix Threads (pthreads)
Widely used threads package Conforms to the Posix standard Sample calls: pthread_create, Typical used in C/C++ applications Can be implemented as user-level or kernel-level or via LWPs
Java Threads
Native thread support built into the language Threads are scheduled by the JVM
Kernel threads:
pthread_create()/pthread_join(): 90 microsec
User-level threads:
pthread_create()/pthread_join(): 5 microsec
Pthreads
a POSIX standard (IEEE 1003.1c) API for thread creation and synchronization. API specifies behavior of the thread library, implementation is up to development of the library. Common in UNIX operating systems.
Linux Threads
Linux refers to them as tasks rather than threads. Thread creation is done through clone() system call. Clone() allows a child task to share the address space of the parent task (process)
Java Threads
Java threads may be created by:
Extending Thread class Implementing the Runnable interface
Java Threads
Thread class Thread worker = new Thread(); Configure it Run it
public class application extends Thread { }
Methods to
Run, wait, exit Cancel, join Synchronize
Threading Issues
Semantics of fork() and exec() system calls for processes Thread cancellation Signal handling Kernel thread implementations
Thread pools Thread specific data Scheduler activations
Not so easy with kernel-level threads Linux has special clone() operation only forking thread is created in child process Windows XP has something similar
Thread Cancellation
Terminating a thread before it has finished Reason:
Some other thread may have completed the joint task E.g., searching a database
Issue:
Other threads may be depending cancelled thread for resources, synchronization, etc. May not be able to cancel one until all can be cancelled
Signal Handling
Signals are used in Unix-Linux to notify process that a particular event has occurred e.g.
Divide-by-zero Illegal memory access, stack overflow, etc. CTL-C typed, or kill command issued at console Timer expiration; external alarm
All processes provided with default signal handler Applications may install own handlers for specific signals
Definition
Task (from point of view of Linux kernel):
Process Thread Kernel thread (see later)
Process Context
0xFFFFFFFF
Kernel Space
stack
(dynamically allocated)
SP2 SP1
stack
(dynamically allocated)
Virtual
address space
User Space
heap
(dynamically allocated)
static data
0x00000000
code (text)
PC
A useful tool
Special kernel thread packages, synchronization primitives, etc. Useful for complex OS environments
Windows XP Threads
Much like to Linux 2.6 threads
Primitive unit of scheduling defined by kernel Threads can block independently of each other Threads can make kernel calls
Synchronization
A program may need multiple threads to share some data. If access is not controlled to be sequential, then shared data may become corrupted.
One thread accesses the data, begins to modify the data, and then is put to sleep because its time slice has expired. The problem arises when the data is in an incomplete state of modification. Another thread awakes and accesses the data, that is only partially modified. The result is very likely to be corrupt data.
Thread Safety
Note that MFC is not inherently thread-safe. The developer must serialize access to all shared data. MFC message queues have been designed to be thread safe. Many threads deposit messages in the queue, the thread that created the (window with that) queue retrieves the messages. For this reason, a developer can safely use PostMessage and SendMessage from any thread. All dispatching of messages from the queue is done by the thread that created the window. Also note that Visual C++ implementation of the STL library is not thread-safe, and should not be used in a multi-threaded environment. I hope that will be fixed with the next release of Visual Studio, e.g., Visual Studio.Net.
MFC Threads
User Interface (UI) threads create windows and process messages sent to those windows Worker threads receive no direct input from the user.
Worker threads must not access a windows member functions using a pointer or reference. This will often cause a program crash. Worker threads communicate with a programs windows by calling the PostMessage and SendMessage functions. Often a program using worker threads will create user defined messages that the worker thread passes to a window to indirectly call some (event-handler) function. Inputs to the function are passed via the messages WPARAM and LPARAM arguments.
AfxBeginThread function that creates a thread: CWinThread *pThread = AfxBeginThread(ThreadFunc, &ThreadInfo); ThreadFunc the function executed by the new thread.
HANDLE hThrd = (HANDLE)_beginthread(ThreadFunc, 0, &ThreadInfo); ThreadFunc the function executed by the new thread void _cdecl ThreadFunc(void *pThreadInfo);
For both threads created with AfxBeginThread and _beginthread the thread function, ThreadFunc, must be a global function or static member function of a class. It can not be a non-static member function.
Suspending and Running Threads SuspendThread. This Suspend a threads execution by calling
increments a suspend count. If the thread is running, it becomes suspended. pThread -> CWinThread::SuspendThread();
Calling ResumeThread decrements the suspend count. When the count goes to zero the thread is put on the ready to run list and will be resumed by the scheduler. pThread -> CWinThread::ResumeThread(); A thread can suspend itself by calling SuspendThread. It can also relinquish its running status by calling Sleep(nMS), where nMS is the number of milliseconds that the thread wants to sleep.
Thread Termination
ThreadFunc returns
Worker thread only Return value of 0 a normal return condition code
WM_QUIT
UI thread only
::GetExitCode(hThread, &dwExitCode)
Returns the exit code of the last work item (thread, process) that has been terminated.
WaitForMultipleObjects makes one thread wait for the elements of an array of kernel objects, e.g., threads, events, mutexes.
Syntax: WaitForMultipleObjects(nCount, lpHandles, fwait, dwMillisec) nCount: number of objects in array of handles lpHandles: array of handles to kernel objects fwait: TRUE => wait for all objects, FALSE => wait for first object dwMillisec: time to wait, can be INFINITE
Process Priority
IDLE_PRIORITY_CLASS
Run when system is idle
NORMAL_PRIORITY_CLASS
Normal operation
HIGH_PRIORITY_CLASS
Receives priority over the preceding two classes
REAL_TIME_PRIORITY_CLASS
Highest Priority Needed to simulate determinism
Thread Priority
You use thread priority to balance processing performance between the interfaces and computations.
If UI threads have insufficient priority the display freezes while computation proceeds. If UI threads have very high priority the computation may suffer. We will look at an example that shows this clearly.
Thread Synchronization
Synchronizing threads means that every access to data shared between threads is protected so that when any thread starts an operation on the shared data no other thread is allowed access until the first thread is done. The principle means of synchronizing access to shared data are:
Interlocked increments only for incrementing or decrementing integers Critical Sections Good only inside one process Mutexes Named mutexes can be shared by threads in different processes. Events Useful for synchronization as well as other event notifications.
Interlocked Operations
InterlockedIncrement increments a 32 bit integer as an atomic operation. It is guaranteed to complete before the incrementing thread is suspended. long value = 5; InterlockedIncrement(&value); InterlockedDecrement decrements a 32 bit integer as an atomic operation: InterlockedDecrement(&value);
Win32 Mutexes
Mutually exclusive access to a resource can be guaranteed through the use of mutexes. To use a mutex object you:
identify the resource (section of code, shared data, a device) being shared by two or more threads declare a global mutex object program each thread to call the mutexs acquire operation before using the shared resource call the mutexs release operation after finishing with the shared resource
MFC Mutexes
A mutex synchronizes access to a resource shared between two or more threads. Named mutexes are used to synchronize access for threads that reside in more than one process.
CMutex constructs a mutex object Lock locks access for a single thread Unlock releases the resource for acquisition by another thread
CMutex cm; cm.Lock(); // access a shared resource cm.Unlock(); CMutex objects are automatically released if the holding thread terminates.
Win32 Events
Events are objects which threads can use to serialize access to resources by setting an event when they have access to a resource and resetting the event when through. All threads use WaitForSingleObject or WaitForMultipleObjects before attempting access to the shared resource. Unlike mutexes and semaphores, events have no predefined semantics.
An event object stays in the nonsignaled stated until your program sets its state to signaled, presumably because the program detected some corresponding important event. Auto-reset events will be automatically set back to the non-signaled state after a thread completes a wait on that event. After a thread completes a wait on a manual-reset event the event will return to the non-signaled state only when reset by your program.
MFC Events
An event can be used to release a thread waiting on some shared resource (refer to the buffer writer/reader example in pages 10181021). A named event can be used across process boundaries. CEvent constructs an event object. SetEvent() sets the event. Lock() waits for the event to be set, then automatically resets it. CEvent ce; : ce.Lock(); // called by reader thread to wait for writer : ce.SetEvent(); // called by writer thread to release reader
Threads Summary
Threads were invented to counteract the heavyweight nature of Processes in Unix, Windows, etc. Provide lightweight concurrency within a single address space Have evolved to become primitive abstraction defined by kernel
Fundamental unit of scheduling in Linux, Windows, etc
Review
Threads introduced because
Processes are heavyweight in Windows and Linux Difficult to develop concurrent applications when address space is unique per process
This problem
is partly an artifact of
Unix, Linux, and Windows
and of
Characteristics
A thread has its own
Program counter, registers, PSW Stack
A thread shares
Address space, heap, static data, program code Files, privileges, all other resources
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
//**************************************************************** // This is a sample threaded program in C++. The main thread creates // 4 daughter threads. Each daughter thread simply prints out a message // before exiting. Notice that Ive set the thread attributes to joinable and // of system scope. //**************************************************************** #include <iostream.h> #include <stdio.h> #include <pthread.h>
#define NUM_THREADS 4 void *thread_function( void *arg ); int main( void ) { int i, tmp; int arg[NUM_THREADS] = {0,1,2,3}; pthread_t thread[NUM_THREADS]; pthread_attr_t attr; // initialize and set the thread attributes pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
// creating threads for ( i=0; i<NUM_THREADS; i++ ) { tmp = pthread_create( &thread[i], &attr, thread_function, (void *)&arg[i] ); if ( tmp != 0 ) { cout << "Creating thread " << i << " failed!" << endl; return 1; } } // joining threads for ( i=0; i<NUM_THREADS; i++ ) { tmp = pthread_join( thread[i], NULL ); if ( tmp != 0 ) { cout << "Joing thread " << i << " failed!" << endl; return 1; } } return 0; }
54 55 56 57 58 59 60 61 62 63 64 65 66
//*********************************************************** // This is the function each thread is going to run. It simply asks // the thread to print out a message. Notice the pointer acrobatics. //*********************************************************** void *thread_function( void *arg ) { int id; id = *((int *)arg); printf( "Hello from thread %d!\n", id ); pthread_exit( NULL ); }
How to compile:
in Linux use:
> {C++ comp} D_REENTRANT hello.cc lpthread o hello
it might also be necessary for some systems to define the _POSIX_C_SOURCE (to 199506L)
Creating a thread:
int pthread_create( pthread_t *thread, pthread_attr_t *attr, *), void *arg ); void *(*thread_function)(void
first argument pointer to the identifier of the created thread second argument thread attributes third argument pointer to the function the thread will execute fourth argument the argument of the executed function (usually a struct) returns 0 for success