Transcript Lecture 6

Threads
By Dr. Yingwu Zhu
Review Multithreading Models



Many-to-one
One-to-one
Many-to-many
Many-to-one Model



Kernels do not support multiple threads of control
Multithreading can be implemented entirely as a
user-level library
Schedule multiple threads onto the process’s single
kernel thread; multiplexing multiple user threads on a
single kernel thread
Many-to-one (cont.): Benefits

Cheap synchronization




When a user thread wishes to perform synchronization, the
user-level thread lib. checks to see if the thread needs to
block.
If a user thread does, the user-level thread lib. enqueues it,
and dequeues another user thread from the lib.’s run queue,
and swithes the active thread.
No system calls are required
Cheap thread creation

The thread lib. need only create a context (i.e., a stack and
registers) and enqueues it in the user-level run queue
Many-to-one (cont.): Benefits

Resource efficiency



Kernel memory is not wasted on a stack for each user thread
Allows as many thread as VM permits
Portability

User-level threads packages are implemented entirely with
standard UNIX and POSIX lib. calls
Many-to-one (cont.): Drawbacks

Single-threaded OS interface




If a user thread blocks (e.g, blocking system calls), the
entire process blocks and so no other user thread can
execute until the kernel thread (which is blocked in the
system call) becomes available
Solution: using nonblocking system calls
Can not utilize MP achitectures
Examples: Java, Netscape
One-to-one Model

Each user thread has a kernel thread
One-to-one (cont.): Benefits

Scalable parallelism


Each kernel thread is a different kernel-schedulable entity;
multiple threads can run concurrently on multiprocessors
Multithreaded OS interface

When one user thread and its kernel thread block, the other
user threads can continue to execute since their kernel
threads are unaffected
One-to-one (cont.): Drawbacks

Expensive synchronization



Kernel threads require kernel involvement to be scheduled;
kernel thread synchronization will require a system call if the
lock is not immediately acquired
If a trap is required, synchronization will be from 3-10 times
more costly than many-to-one model
Expensive creation


Every thread creation requires explicit kernel involvement
and consumes kernel resources
3-10 times more expensive than creating a user thread
One-to-one (cont.): Drawbacks

Resource inefficiency



Every thread created by the user requires kernel memory for
a stack, as well as some sort of kernel data structure to
keep track of it
Many parts of many kernels cannot be paged out
The presence of kernel threads is likely to displace physical
memory for applications
Many-to-Many Model




Combing the previous two models
User threads are multiplexed on top of kernel threads
which in turn are scheduled on top of processors
Taking advantage of the previous two models while
minimizing both’s disadvantages
Creating a user thread does not necessarily require
the creation of a kernel threads; synchronization can
be purely user-level
Pthread Tutorial


Creating and destroying threads
How to use POSIX threads
How to compile?

$ gcc –o proj2 proj2.c –pthread


The option specifies that pthreads library should be linked
causes the complier to properly handle multiple threads in
the code that it generates
Creating and Destroying Threads

Creating threads



Step 1: create a thread
Step 2: send the thread one or more parameters
Destroy threads


Step 1: destroy a thread
Step 2: retrieve one or more values that are returned from
the thread
Creating Threads


-
-
#include <pthread.h>
int pthread_create (pthread_t *thread_id,
pthread_attr_t *attr, void *(*thread_fun)(void *),
void *args);
The #1 para returns thread ID
The #2 para pointing to thread attr. NULL represents using the
default attr. settings
The #3 para as pointer to a function the thread is to execute
The #4 para is the arguments to the function
Thread Terminates


Pthreads terminate when the function returns, or the
thread calls pthread_exit()
int pthread_exit(void *status);


status is the return value of the thread
A thread_fun returns a void*, so calling “return (void *) is
the equivalent of this function
Thread termination



One thread can wait (or block) on the termination of
another by using pthread_join()
You can collect the exit status of all threads you
created by pthread_join()
int pthread_join(pthread_t thread_id, void **status)


pthread_t pthread_self();


The exit status is returned in status
Get its own thread id
int pthread_equal(pthread_t t1, pthread_t t2);

Compare two thread ids
Example
#include <pthread.h>
void *thread_fun(void *arg) {
int *inarg = (int *)arg;
…
return NULL;
}
Int main() {
pthread_t tid;
void *exit_state;
int val = 42;
pthread_create(&tid, NULL, thread_fun, &value);
pthread_join(tid, &exit_state);
return 0;
}
Kill Threads


Kill a thread before it returns normally using
pthread_cancel()
But


Make sure the thread has released any local resources;
unlike processes, the OS will not clean up the resources
Why? Threads in a process share resources
Exercise

Write a multithreaded program that calculates the
summation of a non-negative integer in a separate
thread


The non-negative integer is from command-line parameter
The summation result is kept in a global variable:
int sum; // shared by threads
Step 1: write a thread function
void *thread_sum(void *arg) {
int i;
int m = (int)(*arg);
sum = 0; //initialization
for (i = 0; i <= sum; i++)
sum += I;
pthread_exit(0);
}
Step 2: write the main()
int sum;
int main(int argc, char *argv[]) {
pthread_t tid;
if (argc != 2) {
printf(“Usage: %s <integer-para>\n”, argv[0]);
}
int i = atoi(argv[1]);
if (i < 0) {
printf(“integer para must be non-negative\n”);
}
pthread_create(&tid, NULL, thread_sum, &i);
pthread_join(tid, NULL);
printf(“sum = %d\n”, sum);
}
return -1;
return -2;
Exercise

Write a program that creates 10 threads. Have each
thread execute thesame function and pass each
thread a unique number. Each thread should print
“Hello, World (thread n)” five times where ‘n’ is
replaced by the thread’s number. Use an array of
pthread t objects to hold the various thread IDs. Be
sure the program doesn’t terminate until all the
threadsare complete. Try running your program on
more than one machine. Are there any differences in
how it behaves?
Returning Results from Threads


Thread function return a pointer to void: void *
Pitfalls in return value
Pitfall #1
void *thread_function ( void *)
{
int code = DEFAULT_VALUE;
return ( void *) code ;
}
Only work in machines where integers can convert to a point
and then back to an integer without loss of information
Pitfall #2
void *thread_function ( void *)
{
char buffer[64];
// fill up the buffer with sth good
return ( void *) buffer;
}
This buffer will disappear as the thread function returns
Pitfall #3
void *thread_function ( void *)
{
static char buffer[64];
// fill up the buffer with sth good
return ( void *) buffer;
}
It does not work in the common case of multiple threads
running the same thread funciton
Right Way
void *thread_function ( void *)
{
char* buffer = (char *)malloc(64);
// fill up the buffer with sth good
return ( void *) buffer;
}
Right Way
int main() {
void *exit_state;
char *buffer;
….
pthread_join(tid, &exit_state);
buffer = (char *) exit_state;
printf(“from thread %d: %s\n”, tid, buffer);
free(exit_state);
}
Exercise

Write a program that computes the square roots of
the integers from 0 to 99 in a separate thread and
returns an array of doubles containing the results. In
the meantime the main thread should display a short
message to the user and then display the results of
the computation when they are ready.
Exercise

In textbook 4.7 and 4.9