✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁ Traditional View of a Process CS 105 Process = process context + code, data, and stack “Tour of the Black Holes of Computing!” Programming with Threads Process context Code, data, and stack stack Program context: SP Data registers shared libraries Condition codes Stack pointer (SP) Topics brk Program counter (PC) run-time heap Threads Kernel context: read/write data Shared variables VM structures PC read-only code/data File descriptor table The need for synchronization brk pointer 0 Synchronizing with semaphores Thread safety and reentrancy Races and deadlocks CS 105 – 2 – Alternate View of a Process A Process With Multiple Threads Process = thread + code, data, and kernel context Multiple threads can be associated with a process Each thread has its own logical control flow (sequence of PC values) Each thread shares the same code, data, and kernel context Thread (main thread) Code and Data Each thread has its own thread id (TID) shared libraries Thread 1 (main thread) Shared code and data Thread 2 (peer thread) stack SP brk run-time heap shared libraries read/write data Thread context: stack 1 stack 2 PC Data registers read-only code/data run-time heap Condition codes 0 read/write data Stack pointer (SP) Thread 1 context: Thread 2 context: Program counter (PC) Data registers read-only code/data Data registers Kernel context: Condition codes Condition codes 0 VM structures SP1 SP2 File descriptor table PC1 PC2 Kernel context: brk pointer VM structures File descriptor table brk pointer CS 105 CS 105 – 3 – – 4 –
✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁ Logical View of Threads Concurrent Thread Execution Threads associated with a process form pool of peers Two threads run concurrently (are concurrent) if their logical flows overlap in time Unlike processes, which form tree hierarchy Otherwise, they are sequential (same rule as for processes) Threads associated with process foo Examples: Process hierarchy Concurrent: A & B, A&C Thread A Thread B Thread C P0 T2 T4 Sequential: B & C T1 P1 shared code, data and kernel context Time sh sh sh T3 T5 foo bar CS 105 CS 105 – 5 – – 6 – Threads vs. Processes Posix Threads (Pthreads) Interface How threads and processes are similar Pthreads: Standard interface for ~60 (!) functions that manipulate threads from C programs Each has its own logical control flow Creating and reaping threads Each can run concurrently (maybe on different cores) � pthread_create, pthread_join Each is context-switched Determining your thread ID How threads and processes are different � pthread_self Threads share code and data, processes (typically) do not Terminating threads Threads are somewhat cheaper than processes � pthread_cancel, pthread_exit � Process control (creating and reaping) is roughly 5–8× as expensive as thread control � exit [terminates all threads], return [terminates current thread] � Linux numbers: Synchronizing access to shared variables » ~160K, 280K, 530K cycles minimum to create and reap a process (three machines) � pthread_mutex_init, pthread_mutex_[un]lock » ~19K, 34K, 100K cycles minimum to create and reap a thread � pthread_cond_init, pthread_cond_[timed]wait CS 105 CS 105 – 7 – – 8 –
✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁ The Pthreads "hello, world" Program Execution of Threaded “hello, world” /* main thread * hello.c - Pthreads "hello, world" program */ Thread attributes #include "csapp.h" Thread ID (usually NULL) call Pthread_create() void *howdy(void *vargp); Pthread_create() returns peer thread Thread arguments int main() { call Pthread_join() pthread_t tid; (void *p) printf() main thread waits for return NULL; Pthread_create(&tid, NULL, howdy, NULL); peer thread to terminate (peer thread Pthread_join(tid, NULL); terminates) exit(0); } Thread routine Pthread_join() returns /* thread routine */ exit() void *howdy(void *vargp) { Thread return value printf("Hello, world!\n"); terminates (void **p) return NULL; main thread and } any peer threads CS 105 CS 105 – 9 – – 10 – Pros and Cons of Thread-Based Designs Shared Variables in Threaded C Programs + Threads take advantage of multicore/multi-CPU H/W Question: Which variables in a threaded C program are shared variables? Answer not as simple as “global variables are shared” and “stack variables are + Easy to share data structures between threads private” E.g., logging information, file cache Definition: A variable x is shared if and only if multiple threads reference + Threads are more efficient than processes some instance of x. – Unintentional sharing can introduce subtle and hard-to-reproduce errors! Requires answers to the following questions: Ease of data sharing is greatest strength of threads, but also greatest weakness What is the memory model for threads? Hard to know what’s shared, what’s private How are variables mapped to memory instances? Hard to detect errors by testing (low-probability failures) How many threads reference each of these instances? CS 105 CS 105 – 11 – – 12 –
✁ ✁ ✁ ✁ ✁ ✁ � ✁ � ✁ ✁ � ✁ ✁ Threads Memory Model Example Program to Illustrate Sharing Conceptual model: char **ptr; /* global */ Each thread runs in larger context of a process /* thread routine */ Each thread has its own separate thread context void *thread(void *vargp) int main() { Thread ID, stack, stack pointer, program counter, condition codes, and general-purpose registers { int myid = (int)vargp; int i; All threads share remaining process context static int svar = 0; pthread_t tid; Code, data, heap, and shared library segments of process virtual address space char *msgs[N] = { Open files and installed handlers printf("[%d]: %s (svar=%d)\n", "Hello from foo", myid, ptr[myid], ++svar); "Hello from bar" Operationally, this model is not strictly enforced: return 0; }; } Register values are truly separate and protected ptr = msgs; for (i = 0; i < 2; i++) But any thread can read and write the stack of any other thread Peer threads reference main thread’s stack Pthread_create(&tid, indirectly through global ptr variable NULL, thread, Mismatch between conceptual and operational model is a source of confusion and errors (void *)i); // Pthread_join omitted Pthread_exit(NULL); } CS 105 CS 105 – 13 – – 14 – Mapping Variable Instances to Memory Mapping Vars to Memory Instances Global var : 1 instance ( ptr [data]) Global variables Local automatic vars : 1 instance: i.m, msgs.m Def: Variable declared outside of a function char **ptr; /* global */ Virtual memory contains exactly one instance of any global variable Local automatic var: 2 instances: myid.p0 [peer thread 0’s stack], Local variables int main() myid.p1 [peer thread 1’s stack] { Def: Variable declared inside function without static attribute int i; pthread_t tid; Each thread stack frame contains one instance of each local variable /* thread routine */ char *msgs[2] = { void *thread(void *vargp) "Hello from foo", Local static variables { "Hello from bar" int myid = (int)vargp; }; Def: Variable declared inside function with the static attribute static int svar = 0; ptr = msgs; Virtual memory contains exactly one instance of any local static variable. for (i = 0; i < 2; i++) printf("[%d]: %s (svar=%d)\n", Pthread_create(&tid, myid, ptr[myid], ++svar); NULL, } thread, (void *)i); Pthread_exit(NULL); Local static var : 1 instance: svar [data] } CS 105 CS 105 – 15 – – 16 –
✁ ✁ Shared Variable Analysis Synchronizing Threads Which variables are shared? Shared variables are handy... Variable Referenced by Referenced by Referenced by instance main thread? peer thread 0? peer thread 1? …but introduce the possibility of nasty synchronization errors. ptr yes yes yes svar no yes yes i.m yes no no msgs.m yes yes yes myid.p0 no yes no myid.p1 no no yes Answer: A variable x is shared iff multiple threads reference at least one instance of x. Thus: ptr , svar , and msgs are shared. i and myid are NOT shared. CS 105 CS 105 – 17 – – 18 – badcnt.c : An Improperly Synchronized Assembly Code for Counter Loop Threaded Program /* thread routine */ unsigned int cnt = 0; /* shared */ void *count(void *arg) ����������������������������������� { for (i = 0; i < NITERS; i++) int main() int i; { cnt++; for (i = 0; i < NITERS; i++) pthread_t tid1, tid2; cnt++; Pthread_create(&tid1, NULL, return NULL; count, NULL); } Pthread_create(&tid2, NULL, Asm code for thread i count, NULL); linux> ./badcnt BOOM! cnt=198841183 movl $100000000, %edx H i : Head Pthread_join(tid1, NULL); .L2: Pthread_join(tid2, NULL); linux> ./badcnt movl cnt(%rip), %eax L i : Load cnt BOOM! cnt=198261801 addl $1, %eax U i : Update cnt if (cnt == (unsigned)NITERS*2) movl %eax, cnt(%rip) S i : Store cnt printf("OK cnt=%d\n", linux> ./badcnt subl $1, %edx cnt); BOOM! cnt=198269672 T i : Tail jne .L2 else cnt should be printf("BOOM! cnt=%d\n", cnt); 200,000,000. return 0; What went wrong?! } CS 105 CS 105 – 19 – – 20 –
Recommend
More recommend