You are on page 1of 35

Ch5: Threads

1
What is a Thread?
Technically, a thread is defined as an independent stream of instructions that can
be scheduled to run by the operating system.
To the software developer, the concept of a function" that runs independently
from its main program may best describe a thread.
To go one step further, imagine a main program that contains a number of
functions. Then imagine all of these functions being able to be scheduled to run
simultaneously and/or independently by the operating system. That would
describe a "multi-threaded" program.
How is this accomplished?
So, in summary, in the UNIX environment a thread:
Exists within a process and uses the process resources.
Has its own independent flow of control as long as its parent process exists
and the OS supports it.
Duplicates only the essential resources that it needs.
May share the process resources with other threads.
Dies if the parent process dies.
Is "lightweight" because most of the overhead has already been accomplished
through the creation of its process.
Because threads within the same process share resources:
Changes made by one thread to shared system resources (such as
closing a file) will be seen by all other threads.
Two pointers having the same value point to the same data.
Reading and writing to the same memory locations is possible, and
therefore requires explicit synchronization by the programmer.
When compared to the cost of creating and managing a process, a thread
can be created with much less operating system overhead. Managing
threads requires fewer system resources than managing processes.
On a single processor, multithreading generally occurs by
time-division multiplexing (as in multitasking). The processor
switches between different threads. This context switching
generally happens frequently enough that the user perceives
the threads or tasks as running at the same time.
On a multiprocessor system, the threads or tasks will actually
run at the same time, with each processor running a
particular thread or task.
4
Single Processor Multiple Processor


For example, the following table compares timing results
for the fork() subroutine and
the pthread_create() subroutine. Timings reflect 50,000
process/thread creations, were performed with
the time utility, and units are in seconds.

6
Relative performance of processes and threads in Unix
7
8
9
All threads within a process share the same address space. Inter-thread
communication is more efficient and in many cases, easier to use than inter-
process communication.
For threads, there is no intermediate memory copy required because threads
share the same address space within a single process. There is no data
transfer, per se. It becomes more of a cache-to-CPU or memory-to-CPU
bandwidth (worst case) situation. These speeds are much higher.
Several common models for threaded programs exist:
Manager/worker: a single thread, the manager assigns work to other threads,
the workers. Typically, the manager handles all input and parcels out work to
the other tasks.
Pipeline: a task is broken into a series of suboperations, each of which is
handled in series, but concurrently, by a different thread. An automobile
assembly line best describes this model.
Peer: similar to the manager/worker model, but after the main thread creates
other threads, it participates in the work.
Shared Memory Model:
All threads have access to the same global, shared memory
Threads also have their own private data
Programmers are responsible for synchronizing access to (protecting) globally
shared data.

11
To summarize,
A thread is an independent stream of instructions running inside the
scope of a process. A task can create several threads which will run
concurrently. Threads live in the same context of their caller.
Since their creation does not require the whole process context to be
replicated in memory (as in fork()), they are way faster than tasks.
They produce lower scheduling overhead.
From the point of view of the developer, a thread is a function. The caller
may thread the function to make it executing concurrently with the main
stream of instructions.
The function can access the whole context of the process it's running
inside, e.g. global variables, open files and other functions.
Each instance has an autonomous local scope. Multiple concurrent
instances of a function will not share local variables then.
Threads have separate memory, meaning that variables declared inside a
thread cannot be accessed by other threads.
Processes cannot access same parts of memory but each thread has
access to global variables.
In terms of programming, you can see threads as lightweight processes,
using less memory.


What you have to #include: pthread.h
types used: pthread_t, pthread_attr_t
thread functions
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void
*(*start_routine)(void *), void *arg)
raises a thread executing the function start_routine (completely ignoring their
arguments and return values). thread is set to the threadID assigned. Thread's attributes
can be disposed by properly setting attr, or let defaults by passing NULL. arg is the only
way to pass arguments to the routine, always passed by reference and casted to void *,
or NULL for no arguments. On success 0 is returned.
void pthread_exit(void *value_ptr)
makes the calling thread terminate, offering value_ptr to who joins the exiting thread.
(see below) In any thread except the one which runs main(), this is completely
equivalent to what the return statement does.
pthread_t pthread_self(void)
returns the thread id.
int pthread_join(pthread_t thread, void **value_ptr)
suspends the calling stream of execution until thread terminates. thread's return status
made available by return of pthread_exit is stored in value_ptr. Returns 0 on success.
Non-zero on failure
14
int pthread_create(pthread_t *tid,
const pthread_attr_t *, void
*(*func)(void *), void *arg);
start function func with argument arg in
new thread
return 0 if ok, >0 if not
careful with arg argument

15
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void *myThread(void *arg)
{
printf("%s\n", (char*)arg);
pthread_exit(NULL);
}

int main (int ac, char * av[])
{
pthread_attr_t * thAttr = NULL;
pthread_t tid;
int i;
int ret;

for (i = 0; i < ac; i++)
pthread_create(&tid, thAttr, myThread, (void*)av[i]);
}

17
Ex:1
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
void *do_one_thing(void *);
void* do_another_thing(void *);
void do_wrap_up(int, int);
int r1 = 0, r2 = 0;
void main(void)
{
pthread_t thread1, thread2;
if (pthread_create(&thread1,
NULL,
do_one_thing,
(void *) &r1) != 0)
{perror("pthread_create"); exit(1);}

if (pthread_create(&thread2,
NULL,
do_another_thing,
(void *) &r2) != 0)
{ perror("pthread_create");exit(1); }
if (pthread_join(thread1, NULL) != 0)
{perror("pthread_join");exit(1);}


if (pthread_join(thread2, NULL) != 0)
{perror("pthread_join");exit(1);}

do_wrap_up(r1, r2);

return 0;
}
18
Ex:2
void do_one_thing(void*pnum_times)
{
int i, j, x;
for (i = 0; i < 4; i++) {
printf("doing one thing\n");
for (j = 0; j < 10; j++) x = x + i;
(*(int*)pnum_times)++;
}
}
void do_another_thing(void *pnum_times)
{
int i, j, x;

for (i = 0; i < 4; i++) {
printf("doing another \n");
for (j = 0; j < 10; j++) x = x + i;
(*(int*)pnum_times)++;
}
}
void do_wrap_up(int one_times, int another_times)
{
int total;
total = one_times + another_times;
printf("All done, one thing %d, another %d for a total of %d\n",
one_times, another_times, total);
}
19
Ex:2
Joining:
"Joining" is one way to accomplish synchronization between threads. For example:
The pthread_join() subroutine blocks the calling thread until the
specified threadid thread terminates.
The programmer is able to obtain the target thread's termination return status if it
was specified in the target thread's call to pthread_exit().
A joining thread can match one pthread_join() call. It is a logical error to attempt
multiple joins on the same thread.

Ex:1
Ex:1
Mutex Variables
Mutex is an abbreviation for "mutual exclusion". Mutex
variables are one of the primary means of
implementing thread synchronization and for
protecting shared data when multiple writes occur.
A mutex variable acts like a "lock" protecting access to
a shared data resource. The basic concept of a mutex
as used in Pthreads is that only one thread can lock (or
own) a mutex variable at any given time. Thus, even if
several threads try to lock a mutex only one thread will
be successful. No other thread can own that mutex
until the owning thread unlocks that mutex. Threads
must "take turns" accessing protected data.

Mutexes can be used to prevent "race"
conditions. An example of a race condition
involving a bank transaction is shown below:

In the above example, a mutex should be used to lock the "Balance" while
a thread is using this shared data resource.
Very often the action performed by a thread owning a mutex is the
updating of global variables. This is a safe way to ensure that when several
threads update the same variable, the final value is the same as what it
would be if only one thread performed the update. The variables being
updated belong to a "critical section".
A typical sequence in the use of a mutex is as follows:
Create and initialize a mutex variable
Several threads attempt to lock the mutex
Only one succeeds and that thread owns the mutex
The owner thread performs some set of actions
The owner unlocks the mutex
Another thread acquires the mutex and repeats the process
Finally the mutex is destroyed
When several threads compete for a mutex, the losers block at that call -
an unblocking call is available with "trylock" instead of the "lock" call.
When protecting shared data, it is the programmer's responsibility to
make sure every thread that needs to use a mutex does so. For example, if
4 threads are updating the same data, but only one uses a mutex, the data
can still be corrupted.

Usually the number of array elements will be determined during the execution of the
program. This is called run-time or dynamic memory allocation. Pointers are
required when using dynamic memory allocation.
An array is dynamically allocated in two stages: First a pointer of the correct type is
declared: type* array_name;
Next a block of memory of the required size is allocated using the malloc()
function: array_name = (type*) malloc(size);
size is an integer corresponding to the number of bytes of memory required for
the array. This size can be obtained by using the sizeof() function which returns the
number of bytes that a given variable type holds. Having finished using the array,
the memory must be emptied so that it can be re-used. This is done with the free()
function as follows:
free( array_name);
If memory is not re-claimed after use, and more and more memory is dynamically
reserved during run-time, then there will be memory-leaks and run-time failures.
The cast to void* prevents the pointer being accidentally reused for some other
purpose elsewhere in the program.
The functions malloc(), free() and sizeof() are provided by the header file stdlib.h.

29
To remember dynamic memory allocation;
The following example illustrates how to dynamically declare and then use an
array called i_array that stores a number of integers that is specified by the user at
run-time:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i;
int* i_array; // this will be the array
int j;
// find out how many integers are required
printf("How many integers? ");
scanf("%d",&i);
// Now allocate memory space
i_array = (int*)malloc(i*sizeof(int)); // allocate storage for the array
// code that uses the new array
for (j=0;j<i;j++)
{
i_array[j]=j; // the pointer can be used as an array
printf("%d\n",i_array[j]);
}
free(i_array); // free up memory for reuse.

return 0;
}

30
Multidimensional arrays
The case of dynamically allocating a multidimensional array using the malloc method is a little
more complicated. A 2-dimensional array can be thought of as a "array of arrays", so first an
array of pointers is made for the rows and then each element of this array is, in turn, a
pointer to an array which forms the columns of the 2D array.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i,nrows,ncols;
// Define the size of the array at run time
printf("Enter number of rows and number of columns ");
scanf("%d %d",&nrows,&ncols);
int** array;
array=(int**)malloc(nrows*sizeof(int*));
for (i=0; i<nrows; i++)
array[i]=(int*)malloc(ncols*sizeof(int));
func(array, nrows, ncols); // some function that uses the array ....
for (i=0; i<nrows; i++)
free(array[i]);
free(array);
return 0;
}


31
This example program illustrates the use of mutex
variables in a threads program that performs a dot
product. The main data is made available to all threads
through a globally accessible structure. Each thread
works on a different part of the data. The main thread
waits for all the threads to complete their
computations, and then it prints the resulting sum.
The main program creates threads which do all the
work and then print out result upon completion.
Before creating the threads, the input data is created.
Since all threads update a shared structure, we need a
mutex for mutual exclusion. The main thread needs to
wait for all threads to complete, it waits for each one
of the threads. We specify a thread attribute value that
allow the main thread to join with the threads it
creates. Note also that we free up handles when they
are no longer needed.


Ex:2
Ex:2
Ex:2

You might also like