170 likes | 186 Views
Chapter 6 (6.7 & 6.9 only) - Threads & Mutex. Threads A Thread is a basic unit of CPU utilization consisting of a program counter, register set and stack space.
E N D
Chapter 6 (6.7 & 6.9 only) - Threads & Mutex • Threads • A Thread is a basic unit of CPU utilization consisting of a program counter, register set and stack space. • Remaining resources (memory space and operating system resources, such as open files) are shared with peer threads within the same process. • A thread is a lightweight process.
By sharing a single process’ memory space and O/S objects a program written with multiple threads requires less overhead at context switch time. This can lead to more efficient use of the CPU for multi-threaded applications. • Figure 6.10: Example of two multi-threaded processes. • Threads can execute the same code with different contexts (different register sets, stack & program counter). • A process has at least one thread.
Extend SOS to support threads through three new calls: • int CreateThread(char *startAddr, char *startStack) - start a new thread; code begins at startAddr and the stack it will use is at startStack. Call returns a Thread ID (tid). • int ExitThread(int returnCode) - similar to a process Exit() call, except the last thread of a process, where it is exactly the process Exit() call. • int WaitThread(int tid) - block the calling thread until the thread specified by tid calls ExitThread(). • Stack management is the responsibility of the thread creator (except for the first thread).
Advantages of Threads • Provide parallel (possibly pseudo-parallel) programming. • Cheaper to create and destroy. • Cheaper to context switch between. • By definition they share memory, meaning shared-memory based IPC is a natural and slow system calls are not needed to communicate. • Good for parallel algorithms that are tightly coupled and that use the same data structures. • Common term: multi-threaded server.
Uses of threads • Partition the activities of a single process (Figure 6.11). • Permit asynchronous I/O within a process -- have a thread wait for the I/O to complete. • Activity replication -- a server process can create threads on demand as services are requested. Great example - a multi-thread web server. Another example: disk block server (Figure 6.12). • Thread implementation (6.7.5): not covered.
Process - has its own address space and is allocated CPU time. • Process with threads - split out the concept of CPU allocation into the thread, leaving the process as a shell to hold everything else. • Threads can be implemented in the operating system or they can be implemented totally within a user process (called user threads). • A user process does not need to be in system mode and use special instructions to swap stack pointers and register sets. A timer helps, though. • User threads are more efficient (no O/S switch).
A process running user threads may run more efficiently but can’t take advantage of any O/S thread calls, since the O/S isn’t aware of the user-level threads. • Kernel threads are threads designed to run within the O/S itself. • Examples of Threads: • O/S-level threads are built into Mach, Solaris, OS/2, Windows NT & most modern UNIXes. • User-level threads can be found all over. Of note -- the Java language runtime supports threads. By extension this means Java-aware browsers also implement user-level threads (Netscape, MSIE, etc.).
Java Threads (taken from chapter 15 of The Java Tutorial). • JavaSOS uses Java threads, so it’s worth covering their specifics. • Java thread definition: a single sequential flow of control within a program. • The Thread class is provided in the java.lang package. • You can either extend the Thread class or in the case of a class already being extended (like the Applet class) you can say the class implements Runnable. • Either way you can then override a number of Thread class methods that affect thread behavior.
Example using extends: class SimpleThread extends Thread { public SimpleThread(String str) { super(str); } public void run() { for (int i = 0; i < 10; i++) { System.out.println(i + “ “ + getName()); try { sleep((int) (Math.random() * 1000)); } catch (InterruptedException e) {} } System.out.println(“DONE! “ + getName()); } }
Example creating two instances of SimpleClass and running them: class TwoThreadsTest { public static void main (String[] args) { new SimpleThread(“Jamacia”).start(); new SimpleThread(“Fiji”).start(); } } • Notice use of overridden Thread method run().
Example using implements: Class Clock extends Applet implements Runnable { public void start() { if (clockThread == null) { // if applet just loaded clockThread = new Thread(this, “Clock”); clockThread.start(); } } public void stop() { clockThread = null; } public void run { while (Thread.currentThread() == clockThread) { repaint(); // (sleep w/ exception handling) // } }
JavaSOS uses implements Runnable for the Java threads. Check these files: AppGUICounter.java, AppTests.java - the three user-level applications are all Java threads. HWSimulation.java:class HWSimulatedDisk HWSimulation.java:class HWTimer SOSStart.java:class SOSStart • JavaSOS uses the suspend() and resume() methods of the Thread class to block one process and start another one running.
Implementation of Mutual Exclusion • Two prior solutions to the mutex problem: use of Message Queues & that pesky ExchangeWord() (more on this later). • 1st solution - disable interrupts while the process is in a critical section (no interrupts == no timer interrupt == no context switch to some other process). Book mentions it works well on a single-processor machine, but neglects the downside of having a user program running with interrupts disabled! • 2nd solution - Use ExchangeWord(): a special hardware instruction that allows one processor to read and modify a word in memory without any other processor getting access (atomic read & modify).
Implementation of Mutual Exclusion • Good for multiprocessor mutex solutions; exists in different forms, such as “Test and Set”. • Software Solutions - no special hardware instructions exist to enforce the mutex. • First solution by Dekker in 1965; complex. • A simpler solution done by Peterson in 1981. Imagine two processes, each with the same structure: void Processn(void) { while (1) { DoSomeStuff(); EnterCriticalSection(0); DoCriticalSectionStuff(); LeaveCriticalSection(0); } }
To work, the processes have to agree that: • A process will not wait forever in EnterCriticalSection(). • Only one process at a time can be in the critical section. • A process will spend a finite time in the critical section. • A process that wants to enter its critical section cannot be made to wait for the other process to enter its critical section. enum { False = 0; True = 1; }; int interested[2] = {False, False}; int turn = 0; void EnterCriticalSection(int this_process) { int other_process = 1 - this_process; interested[this_process] = True; turn = this_process; while (turn == this_process && interested[other_process]) { // do nothing (busy loop) } }
void LeaveCriticalSection(int this_process) { interested[this_process] = False; } • This algorithm works with two processes contending for the critical section and it also works if there’s only one process. • It’s important you understand how this code works!
Disabling Interrupts • Fast! • No busy waiting! • Uniprocessor-only solution! • Best solution for a single processor (with the exception you don’t want the interrupts to be disable-able within user mode). • Using ExchangeWord() • Busy waiting :( • Requires hardware assistance (memory subsystem) :( • Works with multiple processors! • Best solution for shared memory multiprocessors. • Peterson’s algorithm (software-only solution) • Busy waiting :( • No hardware assistance required! • Works with multiple processors! • Best solution for distributed systems with no centralized control.