Operating Systems Practical Session 4 Threads 1 Threads

  • Slides: 40
Download presentation
Operating Systems Practical Session 4 Threads 1

Operating Systems Practical Session 4 Threads 1

Threads • Executed within a process. • Allow multiple independent executions under the same

Threads • Executed within a process. • Allow multiple independent executions under the same process (container). • Possible states: running, ready, blocked, terminated. • In most of today’s operating systems, a process is created with at least one thread but may have more than one thread (multithreading). 2

Threads - Advantages • Share open files, data structures, global variables, child processes, etc.

Threads - Advantages • Share open files, data structures, global variables, child processes, etc. • Peer threads can communicate without using System communicate calls. • Threads are faster to create/terminate/switch than faster processes (have no resources attached). • Parallelism which improves overall performance: Parallelism – A single core CPU and a substantial amount of computing and I/O – Multiple cores 3

Threads - Disadvantages • Share open files, data structures, global variables, Share child processes,

Threads - Disadvantages • Share open files, data structures, global variables, Share child processes, etc. • No protection between threads – one can protection read/write/wipe out/corrupt the other’s data. • Sending some signals (such as SIGSTOP) to a process affects all threads running within it. 4

Threads vs. Processes (“classic” approach – Linux’s clone results in some ambiguity) Processes Threads

Threads vs. Processes (“classic” approach – Linux’s clone results in some ambiguity) Processes Threads unique open I/O shared open I/O unique signal table shared signal table unique stack unique PC unique registers unique state heavy context switch light context switch Notice, signal handlers must be shared among all threads of a multithreaded application. 5

Threads – some known Issues • Does the fork() command duplicate just the calling

Threads – some known Issues • Does the fork() command duplicate just the calling thread or all threads of the process? – POSIX defines that only the calling thread is replicated in the child process. – Solaris 10 defines fork 1() and forkall() which attempt to better define the relation between fork and threads, however, this is not POSIX compliant. – Many issues arise from using fork in a multithreaded code. Ø Unless calling exec immediately after fork, try to avoid it! • Does the exec() command replace the entire process? – The entire process is replaced including all its threads. 6

User-level and Kernel-level Threads Library User space Kernel space P P (a) Pure user-level

User-level and Kernel-level Threads Library User space Kernel space P P (a) Pure user-level (b) Pure kernel-level 7

User-level and Kernel-level Threads Library P User space Kernel space P (c) Combined Source:

User-level and Kernel-level Threads Library P User space Kernel space P (c) Combined Source: Stallings, Operating Systems: Internals and design principles 7 th ed. 8

User-level threads • The kernel sees just the main thread of the process (all

User-level threads • The kernel sees just the main thread of the process (all other threads that run within the process’ context are “invisible” to the kernel). • The user application – not the kernel – is responsible for scheduling CPU time for its internal threads within the running time scheduled for it by the kernel. 9

User-level threads (cont’d) • User-level threads implement in user-level libraries, rather than via systems

User-level threads (cont’d) • User-level threads implement in user-level libraries, rather than via systems calls, so thread switching does not need to call operating system and to cause interrupt to the kernel. • The kernel’s inability to distinguish between user level threads makes it difficult to design preemptive scheduling. • If a thread makes a blocking system call, the entire process is blocked. • Will only utilize a single CPU. 10

Kernel-level threads • All threads are visible to the kernel. • The kernel manages

Kernel-level threads • All threads are visible to the kernel. • The kernel manages the threads. • The kernel schedules each thread within the time-slice of each process. • The user cannot define the scheduling policy. • Context switching is slower for kernel threads than for user-level threads. • Because the kernel is aware of the threads, in multiple CPU machines, each CPU can run a different thread of the same process, at the same time. 11

User-level vs. kernel-level threads User-level threads Kernel-level threads Threads Invisible to the kernel Visible

User-level vs. kernel-level threads User-level threads Kernel-level threads Threads Invisible to the kernel Visible to the kernel Scheduling policy User defined Kernel defined Thread switching Non-preemptive* Preemptive Context switch Faster, done by the runtime Slower, done by the kernel Blocking calls Block the whole process Block the single thread Thread table Held by the process Held by the kernel 12

Threads in POSIX (pthreads) int pthread_create( pthread_t* thread, pthread_attr_t* attr, void* (*start_func)(void*) , void*

Threads in POSIX (pthreads) int pthread_create( pthread_t* thread, pthread_attr_t* attr, void* (*start_func)(void*) , void* arg) Creates a new thread of control that executes concurrently with the calling thread. The identifier of the newly created thread is stored in the location pointed by the thread argument, and a 0 is returned. attr specifies thread attributes that will be applied to the new thread (e. g. detached, scheduling-policy). Can be NULL (default attributes). start_func is pointer to the function the thread will start executing; arg contains parameter to the funtion func. Thread type is platform-specific pthread_t pthread_self() return this thread’s identifier. 13

Threads in POSIX (pthreads) – cont’d int pthread_join( pthread_t th, void** thread_return ) Suspends

Threads in POSIX (pthreads) – cont’d int pthread_join( pthread_t th, void** thread_return ) Suspends the execution of the calling thread until the thread identified by th terminates. th is the identifier of the thread that needs to be waited for. At most one thread can wait for the termination of a given thread. The return value of th is stored in the location pointed by thread_return void pthread_exit( void* ret_val ) Terminates the execution of the calling thread. Doesn’t terminate the whole process if called from the main function. If ret_val is not null, then ret_val is saved, and its value is given to the thread who performed join on this thread; that is, it will be written to the thread_return parameter in the pthread_join call. 14

Hello World! #include <pthread. h> #include <stdio. h> When compiling a multi-threaded app: gcc

Hello World! #include <pthread. h> #include <stdio. h> When compiling a multi-threaded app: gcc –D_REENTRANT –o myprog. c –lpthread void *printme(void *arg) { printf("Hello World!n"); return NULL; } void main() { pthread_t tcb; void *status; if (pthread_create(&tcb, NULL, printme, NULL) != 0) { perror("pthread_create"); exit(1); } if (pthread_join(tcb, &status) != 0) { perror("pthread_join"); exit(1); What can happen if we } remove the join part? } 15

Example A – Version 1 void *printme(void *id) { int i = *((int *)id);

Example A – Version 1 void *printme(void *id) { int i = *((int *)id); printf("Hi. I'm thread %dn", i); return NULL; } void main() { int i, vals[4]; pthread_t tids[4]; void *retval; for (i = 0; i < 4; i++) { vals[i] = i; pthread_create(tids+i, NULL, printme, vals+i); } for (i = 0; i < 4; i++) { printf("Trying to join with tid%dn", i); pthread_join(tids[i], &retval); printf("Joined with tid%dn", i); } } 16

Example A – Version 1 possible output Trying to join with tid 0 Hi.

Example A – Version 1 possible output Trying to join with tid 0 Hi. I'm thread 1 Hi. I'm thread 2 Hi. I'm thread 3 Joined with tid 0 Trying to join with tid 1 Joined with tid 1 Trying to join with tid 2 Joined with tid 2 Trying to join with tid 3 Joined with tid 3 17

Example A – Version 2 void *printme(void *id) { int *i; i = (int

Example A – Version 2 void *printme(void *id) { int *i; i = (int *)id; printf("Hi. I'm thread %dn", *i); pthread_exit(NULL); } void main() { int i, vals[4]; pthread_t tids[4]; void *retval; for (i = 0; i < 4; i++) { vals[i] = i; pthread_create(tids+i, NULL, printme, vals+i); } for (i = 0; i < 4; i++) { printf("Trying to join with tid%dn", i); pthread_join(tids[i], &retval); printf("Joined with tid%dn", i); } pthread_exit(NULL); } 18

Example A – Version 2 possible output Trying to join with tid 0 Hi.

Example A – Version 2 possible output Trying to join with tid 0 Hi. I'm thread 1 Hi. I'm thread 2 Hi. I'm thread 3 Joined with tid 0 Trying to join with tid 1 Joined with tid 1 Trying to join with tid 2 Joined with tid 2 Trying to join with tid 3 Joined with tid 3 19

Example A – Version 3 void *printme(void *id) { int *i; i = (int

Example A – Version 3 void *printme(void *id) { int *i; i = (int *)id; printf("Hi. I'm thread %dn", *i); pthread_exit(NULL); } void main() { int i, vals[4]; pthread_t tids[4]; void *retval; for (i = 0; i < 4; i++) { vals[i] = i; pthread_create(tids+i, NULL, printme, vals+i); } pthread_exit(NULL); for (i = 0; i < 4; i++) { printf("Trying to join with tid%dn", i); pthread_join(tids[i], &retval); printf("Joined with tid%dn", i); } } 20

Example A – Version 3 output Hi. I'm thread 0 Hi. I'm thread 1

Example A – Version 3 output Hi. I'm thread 0 Hi. I'm thread 1 Hi. I'm thread 2 Hi. I'm thread 3 If the main thread calls pthread_exit(), the process will continue executing until the last thread terminates or the whole process is terminated 21

Example A – Version 4 void *printme(void *id) { int *i = (int *)id;

Example A – Version 4 void *printme(void *id) { int *i = (int *)id; sleep(5); printf("Hi. I'm thread %dn", *i); pthread_exit(NULL); } int main() { int i, vals[4]; pthread_t tids[4]; void *retval; for (i = 0; i < 4; i++) { vals[i] = i; pthread_create(tids+i, NULL, printme, vals+i); } return 0; } 22

Example A – Version 4 possible output No Output! 23

Example A – Version 4 possible output No Output! 23

Example A – Version 5 void *printme(void *id) { int *i; i = (int

Example A – Version 5 void *printme(void *id) { int *i; i = (int *)id; printf("Hi. I'm thread %dn", *i); exit(0); } main() { int i, vals[4]; pthread_t tids[4]; void *retval; for (i = 0; i < 4; i++) { vals[i] = i; pthread_create(tids+i, NULL, printme, vals+i); } for (i = 0; i < 4; i++) { printf("Trying to join with tid%dn", i); pthread_join(tids[i], &retval); printf("Joined with tid%dn", i); } pthread_exit(NULL); } 24

Example A – Version 5 possible output Trying to join with tid 0 Hi.

Example A – Version 5 possible output Trying to join with tid 0 Hi. I'm thread 0 25

Synchronization 26

Synchronization 26

Motivation • Multiprocessing needs some tools for managing shared resources – Printers – Files

Motivation • Multiprocessing needs some tools for managing shared resources – Printers – Files – Data Bases 27 27

Conditions for a good Solution 1. Mutual Exclusion – No two processes are in

Conditions for a good Solution 1. Mutual Exclusion – No two processes are in the critical section (CS) at the same time 2. Deadlock Freedom – If processes are trying to enter the CS one will eventually enter it 3. Starvation Freedom – When a process tries to enter its CS, it will eventually succeed 28 28

Solution Archetypes • Busy wait – Wastes CPU – Priority inversion with busy waiting

Solution Archetypes • Busy wait – Wastes CPU – Priority inversion with busy waiting (*): Task L (low priority) runs and gains exclusive use of resource R. H (high priority) task is introduced and attempts to acquire R and must therefore wait. Note that since H is busy waiting it may still be scheduled (its state remains runnable). Result: L can’t run and release R because H is of higher priority and is scheduled to run before it. H on the other hand can’t proceed past its busy wait loop. Deadlock! • Sleep & Wake up – Also prone to priority inversion but will not deadlock. Can you think of a scenario? 29 29

XV 6 30

XV 6 30

XV 6 - Spinlock spinlock. h // Mutual exclusion lock. struct spinlock { uint

XV 6 - Spinlock spinlock. h // Mutual exclusion lock. struct spinlock { uint locked; // Is the lock held? // For debugging: char *name; // Name of lock. struct cpu *cpu; // The cpu holding the lock. uint pcs[10]; // The call stack (an array of program counters) // that locked the lock. }; 31 31

XV 6 - Spinlock XV 6 uses an atomic x 86 operation called xchg:

XV 6 - Spinlock XV 6 uses an atomic x 86 operation called xchg: Int xchg(int *addr, int value) { int temp = *addr; *addr = value; return temp; } 32 32

XV 6 - Spinlock spinlock. c // Acquire the lock. // Loops (spins) until

XV 6 - Spinlock spinlock. c // Acquire the lock. // Loops (spins) until the lock is acquired. // Holding a lock for a long time may cause // other CPUs to waste time spinning to acquire it. void acquire(struct spinlock *lk) { pushcli(); // disable interrupts to avoid deadlock. if(holding(lk)) panic("acquire"); // The xchg is atomic. // It also serializes, so that reads after acquire are not // reordered before it. while(xchg(&lk->locked, 1) != 0) ; // Tell the C compiler and the processor to not move loads or stores // past this point, to ensure that the critical section's memory // references happen after the lock is acquired. __synchronize(); // Record info about lock acquisition for debugging. lk->cpu = cpu; getcallerpcs(&lk, lk->pcs); } 33 spinlock. c // Release the lock. void release(struct spinlock *lk) { if(!holding(lk)) panic("release"); lk->pcs[0] = 0; lk->cpu = 0; // Tell the C compiler and the processor to not move loads or stores // past this point, to ensure that all the stores in the critical // section are visible to other cores before the lock is released. // Both the C compiler and the hardware may re-order loads and // stores; __synchronize() tells them both not to. __synchronize(); // Release the lock, equivalent to lk->locked = 0. // This code can't use a C assignment, since it might // not be atomic. A real OS would use C atomics here. asm volatile("movl $0, %0" : "+m" (lk->locked) : ); popcli(); } 33

XV 6 - Scheduler proc. c // Per-CPU process scheduler. // Each CPU calls

XV 6 - Scheduler proc. c // Per-CPU process scheduler. // Each CPU calls scheduler() after setting itself up. // Scheduler never returns. It loops, doing: // - choose a process to run // - swtch to start running that process // - eventually that process transfers control // via swtch back to the scheduler. void scheduler(void) { struct proc *p; for(; ; ){ // Enable interrupts on this processor. sti(); // Loop over process table looking for process to run. acquire(&ptable. lock); for(p = ptable. proc; p < &ptable. proc[NPROC]; p++){ if(p->state != RUNNABLE) continue; 34 proc. c // Switch to chosen process. It is the process's job // to release ptable. lock and then reacquire it // before jumping back to us. proc = p; switchuvm(p); p->state = RUNNING; swtch(&cpu->scheduler, proc->context); switchkvm(); // Process is done running for now. // It should have changed its p->state before coming back. proc = 0; } release(&ptable. lock); } } 34

Homework Exercises 43

Homework Exercises 43

Midterm – 2006 (cont’d) : ( פתרון )א 1 5 4 2 6 3

Midterm – 2006 (cont’d) : ( פתרון )א 1 5 4 2 6 3 45