11-16 Flashcards
(153 cards)
Thread: A new abstraction for __.
a single running process
Multi-threaded program:
(3)…
• A multi-threaded program has more than one point of execution
- Multiple program counters, one for each thread
• They share the same address space
• Each thread has its own stack
• Each thread has its own private set of registers
Context switch between threads:
Each thread has its own __.
- …
program counter and set of registers;
One or more thread control blocks (TCBs) are needed to store the state of each thread
Context switch between threads:
When switching from running one thread (T1) to running the another (T2):
(3)…
- The register state of T1 be saved
- The register state of T2 restored
- The address space remains the same
Why Use Threads?:
(2)…
- Parallelism
• Divide a task among several threads
• On a system with multiple processors threads can work in parallel with each other - Overlap of I/O with other activities within a single program
• When one thread requests I/O from the system, switch to another thread ready to work
• Similar to multiprogramming across different processes
Parallelism:
(2)…
- Divide a task among several threads
* On a system with multiple processors threads can work in parallel with each other
Overlap of I/O with other activities within a single program:
(2)…
- When one thread requests I/O from the system, switch to another thread ready to work
- Similar to multiprogramming across different processes
The stack of the relevant thread:
There will be one stack per thread:
A Single-Threaded Address Space:
(3)…
- The code segment:
where instructions live - The heap segment:
contains malloc’d data dynamic data structures (it grows downward) - The stack segment:
(it grows upward) contains local variables arguments to routines, return values, etc.
Example: Creating a Thread
#include [LTS]pthread.h> void *mythread(void *arg) { printf("%s\n", (char *) arg); return NULL; } int main(int argc, char *argv[]) { if (argc != 1) { fprintf(stderr, "usage: main\n"); exit(1); } pthread_t p1, p2; printf("main: begin\n"); Pthread_create(&p1, NULL, mythread, "A"); Pthread_create(&p2, NULL, mythread, "B"); // join waits for the threads to finish Pthread_join(p1, NULL); Pthread_join(p2, NULL); printf("main: end\n"); return 0; }
How to create and control threads?: #include [LTS]pthread.h> int pthread_create( pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg);
• thread: \_\_ • attr: \_\_ - \_\_ • start_routine: \_\_ • arg: \_\_ - a void pointer allows us to \_\_ • return value: \_\_
• thread: Used to interact with this thread
• attr: Used to specify any attributes this thread might have
- Stack size, Scheduling priority, …
• start_routine: the function this thread start running in
• arg: the argument to be passed to the function (start routine)
- a void pointer allows us to pass in any type of argument
• return value: on success, returns 0; on error, it returns an error number, and the contents of *thread are undefined
Wait for a thread to complete: int pthread_join(pthread_t thread, void **value_ptr);
• thread: __
• value_ptr: __
- Because pthread_join() routine changes the value, __
• thread: Specify which thread to wait for
• value_ptr: A pointer to the return value
- Because pthread_join() routine changes the value, you need to pass in a pointer to that value
Example: Dangerous code
• Be careful with __ from a thread
(2)…
how values are returned
- Variable r is allocated on the stack
- ## When the thread returns, r is automatically de-allocatedvoid *mythread(void *arg) {
myarg_t *m = (myarg_t *) arg;
printf(“%d %d\n”, m->a, m->b);
myret_t r; // Danger!!!!! Why is this bad?
r.x = 1;
r.y = 2;
return (void *) &r;
8 }
Example: Simpler Argument Passing to a Thread
Just passing in a single value: void *mythread(void *arg) { int m = (int) arg; printf(“%d\n”, m); return (void *) (arg + 1); }
int main(int argc, char *argv[]) { pthread_t p; int rc, m; pthread_create(&p, NULL, mythread, (void *) 100); pthread_join(p, (void **) &m); printf(“returned %d\n”, m); return 0; }
Threading: Shared Variables
static volatile int counter = 0; // shared global variable // mythread() // Simply adds 1 to counter repeatedly, in a loop. No, this is not how you would add 10,000,000 to a counter, // but it shows the problem nicely. void *mythread(void *arg) { printf("%s: begin\n", (char *) arg); int i; for (i = 0; i < 1e7; i++) { counter = counter + 1; } printf("%s: done\n", (char *) arg); return NULL; // main() // Just launches two threads (pthread_create) // and then waits for them (pthread_join) int main(int argc, char *argv[]) { pthread_t p1, p2; printf("main: begin (counter = %d)\n", counter); Pthread_create(&p1, NULL, mythread, "A"); Pthread_create(&p2, NULL, mythread, "B"); Pthread_join(p1, NULL); // join waits for the threads to finish Pthread_join(p2, NULL); printf("main: done with both (counter = %d)\n", counter); return 0; }
Race condition:
Example with two threads and counter = 50
• counter = __
• We expect the result to be __
counter + 1 (runs twice, once for each thread);
52
Critical section:
(3)…
- A piece of code that accesses a shared variable and must not be concurrently executed by more than one thread
- Multiple threads executing critical section can result in a race condition
- Need to support atomicity for critical sections (mutual exclusion)
Locks ensure that any such critical section executes as if…
• An example: the canonical update of a shared variable:
balance = balance + 1;
• Add some code around the critical section:
lock_t mutex; // some globally-allocated lock ‘mutex’
…
lock(&mutex);
balance = balance + 1;
unlock(&mutex);
Locks provide __ to a critical section.
- Interface
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex); - Usage (w/o lock initialization and error check)
pthread_mutex_t lock;
pthread_mutex_lock(&lock);
x = x + 1; // or whatever your critical section is
pthread_mutex_unlock(&lock);
- No other thread holds the lock → the thread will acquire the lock and enter the critical section
- If another thread hold the lock → the thread will not return from the call until it has acquired the lock
- No other thread holds the lock → __
* If another thread hold the lock → __
the thread will acquire the lock and enter the critical section;
the thread will not return from the call until it has acquired the lock
Lock Initialization:
All locks must be __.
2 ways:
…
- One way: using PTHREAD_MUTEX_INITIALIZER
- The dynamic way: using pthread_mutex_init()
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int rc = pthread_mutex_init(&lock, NULL);
assert(rc == 0); // always check success!
Lock Error Checking:
• Always check the __.
return value for errors when calling lock and unlock --- • An example wrapper // Use this to keep your code clean but check for failures // Only use if exiting program is OK upon failure void Pthread_mutex_lock(pthread_mutex_t *mutex) { int rc = pthread_mutex_lock(mutex); assert(rc == 0); }
Condition variables are useful when __.
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_cond_signal(pthread_cond_t *cond);
- pthread_cond_wait
(2) … - pthread_cond_signal
(1) …
- Put the calling thread to sleep
- Wait for some other thread to signal it;
• Unblock at least one of the threads that are blocked on the condition variable
Condition Variable Example: A thread calling wait routine: pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t init = PTHREAD_COND_INITIALIZER; pthread_mutex_lock(&lock); while (initialized == 0) pthread_cond_wait(&init, &lock); pthread_mutex_unlock(&lock);
(2)…
• The wait call releases the lock when putting said caller to sleep
• Before returning after being woken, the wait call re-acquire the lock
—
• A thread calling signal routine
pthread_mutex_lock(&lock);
initialized = 1;
pthread_cond_signal(&init);
pthread_mutex_unlock(&lock);