; Threads
Documents
Resources
Learning Center
Upload
Plans & pricing Sign in
Sign Out

Threads

VIEWS: 3 PAGES: 80

  • pg 1
									Unix Synchronization

 * Mutexes
 * Semaphores
   - named
   - unnamed
First we will study thread
 synchronization in Posix

Posix thread synchronization
  is done with mutex locks
First, a quick review of threads ...
operating
 systems

            Consider the following code

            #include <stdio.h>            void print_msg(char* m)
                                          {
            void print_msg(char*);           int i;
                                             for (i = 0; i < 5; i++)
            int main ( )                     {
            {                                   printf(“%s”, m);
                print_msg(“hello “);            fflush( stdout );
                print_msg(“world\n”);           sleep(1);
                                              }
                return 0;                 }
            }
operating
 systems




            This program will produce


            hello hello hello hello hello world
            world
            world
            world
            world
operating         The thread of execution is
 systems



            main( )                   print_msg( )
operating         With multiple threads of execution
 systems



            main( )                  print_msg( )
                                                 void print_msg(void* m)
operating
                Consider the following code      {
 systems                                            char* cp = (char*) m;
                                                    int i;
            #include <stdio.h>                      for (i = 0; i < 5; i++)
            #include <pthread.h>                    {
                                                       printf(“%s”, cp);
            void *print_msg(void *);                   fflush( stdout );
                                                       sleep(1);
            int main ( )                             }
            {                                    }
                pthread thr1, thr2;

                 pthread_create(&thr1, NULL, print_msg, (void *)”hello “);
                 pthread_create(&thr2, NULL, print_msg, (void *)”world\n “);
                 pthread_join(thr1, NULL);
                 pthread_join(thr2, NULL);

                 return 0;
            }
operating
 systems




            This program will produce


            hello world
            hello world
            hello world
            hello world
            hello world
operating
 systems                          the address       the function
                                 of the thread      to run on the
                                                       thread




                   pthread_create(&thr1, NULL, print_msg, (void *)”hello “);




            start a new thread        use default
                                        thread                the function
               of execution.
                                      attributes               parameter
operating
 systems

                                 the name of
                                  the thread




                   pthread_join(thr1, NULL);




            wait here for this   where to store any
             thread to finish       return value
operating
 systems


                 Thread Communication

            One of the advantages of threads is that they share
            the same address space. Threads can communicate by
            reading and writing to that shared space.
operating
 systems    static int globalValue = 0;           our shared variable

            int main ( )
            {
              int i;

                pthread_t mythread;
                pthread_create(&mythread, NULL, funny_add, NULL);
                for (i = 0; i < 10; i++)
                {
                  globalValue++;
                  sleep(1);
                }

                pthread_join(mythread, NULL);
                printf(“\nDone ...global value = %d\n”, globalValue);
                return 0;
            }
void *funny_add(void *arg)
{
  int j;
  for (j = 0; j < 10; j++)
  {
    int temp = globalValue + 1;
    sleep(1);
    globalValue = temp;
  }
}
operating             Using a shared variable
 systems



             main( )                      funny_add( )
                  pthread_create


                                                         temp = globalValue + 1
            globalValue++                                sleep
                    sleep                                globalValue = temp



                    pthread_join




                                    globalValue
operating
 systems



            This program seems to work okay, but it has
                  a potential problem .... what is it?


               There is no thread synchronization. We cannot
               guarantee the order in which the threads are
               executed.

                     Let’s Try It - exampleOne.c
Hmmmm....

Let’s add some print statements so that we
can see how the threads are executing.
            static int globalValue = 0;            our shared variable
operating
 systems    int main ( )
            {
              int i;

                pthread_t mythread;
                pthread_create(&mythread, NULL, funny_add, NULL);
                for (i = 0; i < 10; i++)
                {
                  fprintf(stderr, “Main thread, globalValue = %s\n”, globalValue);
                  globalValue++;
                  fprintf(stderr, “Main thread, incr globalValue = %s\n”, globalValue);

                    sleep(1);
                }

                pthread_join(mythread, NULL);
                printf(“\nDone ...global value = %d\n”, globalValue);
                return 0;
            }
void *funny_add(void *arg)
{
  int j;
  for (j = 0; j < 10; j++)
  {
    fprintf(stderr, “in funny_add, globalValue = %d\n”, globalValue);
    int temp = gloablValue + 1;
    sleep(1);
    globalValue = temp;
    fprintf(stderr, “in funny_add, incr globalValue = %d\n”, globalValue);

    }
}
              Mutexes
A mutex is a special variable that can either
be in a locked state or an unlocked state.

If a mutex is locked, it has a distinguished
thread that holds or owns the mutex.

If no thread holds the mutex, it is unlocked,
free, or available.

A mutex also has a queue for threads that
are waiting to hold the mutex. The order in
which the threads in the queue obtain the
mutex is determined by the OS’s thread
scheduling policy.
The mutex is the simplest and most efficient
thread synchronization mechanism.

A mutex is meant to be held for short
periods of time.

A thread that is waiting for a mutex is not
logically interruptible, except by termination
of the process or termination of the thread.

Mutex locks are ideal for making changes to
data structures in which the state of the
data structure is temporarily inconsistent.
    Creating and initializing a
             mutex
 In posix mutexes are declared with the data type
 pthread_mutex_t.

A program must always initialize a mutex before it is used.
A program can initialize a mutex in one of two ways:


  using the static initializer PTHREAD_MUTEX_INITIALIZER
  this method is most efficient, and is guaranteed
  to only be executed exactly once before any threads
  begin execution. Use it for statically allocated mutex variables.


  calling pthread_mutex_init ( ) for dynamically allocated mutex
  variables.
         pthread_mutex_init (&my_lock, NULL);
        Dynamic Initialization
             returns 0 if successful


int pthread_mutex_init(pthread_mutex_t *mymutex,
                          const pthread_mutexattr_t *attr);



pointer to a dynamically allocated           pointer to mutex attributes
region of memory to store the mutex.         okay to pass NULL



    when you initialize a mutex with pthead_mutex_init,
    you must destroy the mutex with

               pthread_mutex_destroy( ).

    Note that it does not free the memory used to store the mutex.
            Static Initialization


pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
                      Locking and Unlocking a
                              Mutex

                int pthread_mutex_lock(pthread_mutex_t *mutex);

                         Blocks until the mutex is available

                int pthread_mutex_trylock(pthread_mutex_t *mutex);
All return 0
if successful            Returns immediately if the mutex is not available


                int pthread_mutex_unlock(pthread_mutex_t *mutex);

                         Releases the mutex
Let’s synchronize our threads with a mutex!
            static int globalValue = 0;
            static pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
operating
 systems    int main ( )
            {
              int i;

                pthread_t mythread;
                pthread_create(&mythread, NULL, funny_add, NULL);
                for (i = 0; i < 10; i++)
                {
                  pthread_mutex_lock(&mymutex);
                  fprintf(stderr, “Main thread, globalValue = %s\n”, globalValue);
                  globalValue++;
                  fprintf(stderr, “Main thread, incr globalValue = %s\n”, globalValue);
                  pthread_mutex_unlock(&mymutex);

                    sleep(1);
                }

                pthread_join(mythread, NULL);
                printf(“\nDone ...global value = %d\n”, globalValue);
                return 0;
            }
operating
 systems
                 A Volunteer Effort

            Locking and unlocking are voluntary and a
            program only achieves mutual exclusion when it’s
            threads correctly acquire the appropriate
            mutex when entering a critical section of code,
            and release the mutex when finished.

            One way of ensuring this is to only allow access
            to shared variables through well defined
            functions. Then put the mutex lock and unlock
            calls in these functions. The locking mechanism
            is then transparent to the calling threads.
                Should I Always Use a
                       Mutex?
operating
 systems


            Consider a program containing a bunch of threads that execute
            the instruction

                     myGlobalValue = myGlobalValue +1;

            Since this statement will likely generate a single machine
            instruction, should we worry about using a mutex?
operating                       Why?
 systems




            Consider an SMP architecture that updates main memory in
            32-bit blocks. Each processor may execute the instruction
            at the same time. The memory modification will then trickle
            down through level 1 and level 2 cache and then to main
            memory. If you are incrementing a 64 bit integer without
            mutexes, the uppermost 4 bytes might come from one
            processor and the lower 4 bytes from the other. Bummer!
operating      How Many Mutexes
 systems


            If you place too many mutexes your code will
            slow down considerably.

            Use mutexes to serialize access to shared
            data. Don’t use them for local data.

            Don’t use them if your program logic ensures
            that only one thread can access shared data
            at a single time.
operating
 systems


            Protecting Unsafe Library
                    Functions

            Many C library functions are not thread-safe. An
            example is the rand( ) function. However, you can safely
            use the rand( ) function in a multi-threaded environment
            if you can guarantee that no two threads are
            concurrently calling it.
operating
 systems


            int randsafe(double *ranp)
            {
                static pthread_mutex_t lock =
                    PTHREAD_MUTEX_INITIALIZER;
                int error;

                if (error = pthread_mutex_lock(&lock))
                    return error;
                *ranp = (rand( ) +0.5) / (RAND_MAX + 1);
                return pthread_mutex_unlock(&lock);
            }
operating
 systems




            Suppose that we want to count the number of words
            in two separate files and add them together, using
            two threads to do the counting for us concurrently.

                                                Molay ch 14
operating
 systems           Counting words with multiple threads of execution



                  main( )                         count_words( )




                            word_count++

                                           word_count++
            word_count
                                                          file1

                                                          file2
operating
 systems



            Suppose that the code generated for

               word_count++;

            looks something like

               load register, word_count
               add register, 1
               store register, word_count
            time
                         thread 1
operating
 systems                                     100
                                          word_count
                   load reg, word_count
            time
                         thread 1                            thread 2
operating
 systems                                     100
                                          word_count
                   load reg, word_count

                           100
                                                       load reg, word_count




                                                           add register, 1

                                                               101


                                                        store reg, word_count
            time
                         thread 1                            thread 2
operating
 systems                                     100
                                          word_count
                   load reg, word_count

                           100
                                                       load reg, word_count

                                                               100


                                                           add register, 1

                                                               101


                                                        store reg, word_count
            time
                         thread 1                             thread 2
operating
 systems                                      101
                                           word_count
                   load reg, word_count

                           100
                                                        load reg, word_count

                                                                100

                                                            add register, 1

                                                                101


                                                         store reg, word_count

                      add register, 1

                          101


                   store reg, word_count
            time
                         thread 1                                 thread 2
operating
                                              100       LOCK
 systems
                                           word_count
                   load reg, word_count

                           100
                                                            this thread gets blocked
                                                           because the shared variable
                                                                    is locked




                      add register, 1

                          101

                                              101       UNLOCK
                   store reg, word_count
                                                            now this thread can run ---
                                           word_count
                                                              and access the shared
                                                               variable word_count
operating
 systems




            You can solve this problem
              without using a mutex!



            Let each thread have its own counter.
               Add these counters at the end.
operating
 systems




            struct argset
            {
               char* fname;   /* the file name */
               int count;     /* the word count */
            };
pthread_t thr1;
struct argset args1;

args1.fname = argv[1];                        pass in the struct
args1.count = 0;                              as the argument


...

pthread_create(&thr1, NULL, count_words, (void *) &args1);

...

//print the sum of the two counters
printf(“%5d total words\n”, args1.count + args2.count);
Now suppose your program is going to count
the words in a very big file and a very small file.
We would like to have some way for each thread
to notify the main thread when it is done counting.
                       Using a Condition Variable
                                            we “signal” that there
                                             is something in the
                                                   mailbox
the mailbox has a lock
on it to prevent two threads
trying to access it at once.
 Main             Counting           Counting
Thread            Thread 1           Thread 2


 * set up a reporting mailbox
   - it has space for one count at a time
   - it has a flag that can be raised, it then snaps back
   - there is a mutex lock to protect the mailbox
 Main            Counting           Counting
Thread           Thread 1           Thread 2


 * unlock the mailbox and wait until the flag goes up
 Main        Counting           Counting
Thread       Thread 1           Thread 2



         * waits until it can lock the mailbox
         * if the mailbox is empty, it puts
           its count in the mailbox
         * It raises the flag on the mailbox
         * Then finally releases its lock
 Main            Counting           Counting
Thread           Thread 1           Thread 2


 * stops waiting when the flag goes up
 * locks the mailbox
 * takes out the count
 * processes the count
 * puts up the flag in case another count thread is waiting
 * unlocks the mailbox and waits
operating
 systems




            struct argset
            {
               char* fname;   /* the file name */
               int count;     /* the word count */
            };
struct argset *mailbox = NULL;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t flag = PTHREAD_COND_INITIALIZER:

int main (int argc, char *argv[ ])
{
  pthread_t thr1, thr2;                   declaring and initializing
  struct argset args1, args2;              the condition variable
  int reports_in = 0;

  ...
  pthread_mutex_lock(&lock);         lock the mutex.
  args1.fname = argv[1];
  args1.count = 0;

  ...
  start a counting thread
  pthread_create(&thr1, NULL, count_words, (void *) &args1);
One of the counting threads . . .



pthread_mutex_lock(&lock);
 The counting thread tries to get the lock.
 It can’t, so it blocks
     Back in the main thread ...

while (reports_in < 2)
{
  pthread_cond_wait(&flag, &lock);
     this statement unlocks the mutex and
     blocks this thread until the flag is signaled.


             This is an atomic action!
One of the counting threads
     now gets the lock


pthread_mutex_lock(&lock);

check to see if the mailbox is empty ... if it is not,
        the thread has to wait until it is.

if (mailbox != NULL)
  pthread_cond_wait(&flag, &lock);
One of the counting threads
     now gets the lock


pthread_mutex_lock(&lock);

if (mailbox != NULL)
  pthread_cond_wait(&flag, &lock);

mailbox = args;   the mailbox is empty.

                  store the address of this thread’s
                  arguments in the mailbox
One of the counting threads
     now gets the lock


pthread_mutex_lock(&lock);

if (mailbox != NULL)
  pthread_cond_wait(&flag, &lock);

mailbox = args;

pthread_cond_signal(&flag); “Raise” the flag on the mailbox
                              The main thread is waiting for
                              this signal!
One of the counting threads
     now gets the lock


pthread_mutex_lock(&lock);

if (mailbox != NULL)
  pthread_cond_wait(&flag, &lock);

mailbox = args;

pthread_cond_signal(&flag);
pthread_mutex_unlock(&lock); Finally, unlock the mutex.
while (reports_in < 2)
{
  pthread_cond_wait(&flag, &lock);

  control returns to this point when the condition variable
  has been signaled and the mutex is available. This call
  automatically re-locks the mutex when it returns.

 total_words += mailbox->count;
 if (mailbox == &args1)
   pthread_join(thr1, NULL);
 mailbox = NULL;
  ...
Semaphores
          Semaphores

There are two types of semaphores
   Binary semaphores – work just like a mutex
   Counting semaphores – use when you have a
   finite set of resources that is being used by
   a set of threads. When a thread needs a
   resource, it decrements the associated
   semaphore. When it is done with the
   resource, the thread increments the
   associated semaphore. Semaphore values
   cannot fall below zero. When a thread tries
   to use a semaphore, it is blocked if the
   semaphore is zero.
Pseudo-code to illustrate how wait and signal work

      void wait (semaphore_t *sp)
      {
         if (sp->value > 0)
              sp->value--;
         else // sp->value is zero
         {
              add this thread to sp->list;
              block;                             These are atomic
         }                                         operations!
      }

       void signal (semaphore_t *sp)
       {
          if (sp->list != NULL)
              remove a thread from the list and put it in ready state;
          else
              sp->value++;
      }
        Semaphore Functions
             for Posix un-named semaphores.


      sem_init ( )    Creates and initializes a
                      semaphore
      sem_wait ( )    decreases semaphore by one
                      if it is non-zero. Otherwise,
                      it waits
      sem_post ( )    increases the semaphore by
                      one




Note: OS X does not currently support unnamed semaphores!
               Semaphore Functions
                        for Posix named semaphores.


                           Declaring a Semaphore

                             sem_t *mySemaphore;

                         Initializing the Semaphore

                    sem_t *sem_open (const char *name, int
                        flags, int perms, int value);
O_CREAT | O_EXECL
                                             The “name” of the semaphore
S_IRUSR | S_IWUSR | …
                                             Allows semaphores to be used
                                             when not sharing memory.
                             Initial value
      Semaphore Functions
            for Posix named semaphores.
Waiting on a Semaphore        If a semaphore is 0, the
                              calling thread blocks until
                              the semaphore becomes
 int sem_wait(sem_t *sem);    non-zero. Then it decrements
                              the semaphore.


Signaling on a Semaphore     If no threads are waiting,
                             it increments the semaphore.
                             If one or more threads are blocked,
 int sem_post(sem_t *sem);   the value remains at zero and one
                             of the waiting threads is unblocked.




                                     Both return 0 if successful
Semaphore Functions
         for Posix named semaphores.




Removing the semaphore from the system:

       int sem_unlink(const char *name);


 Be sure that you include this function in your code
 to unlink any semaphores! Otherwise they persist
 beyond the lifetime of your program.
The sleeping Barber Problem
        A classic Synchronization Problem



                                            There is one Barber
                                            and one Barber Chair




There are n chairs in the waiting room
 If there are no customers
the barber sits in the chair
      and goes to sleep.
                                              If there are no customers
                                             the barber sits in the chair
                                                   and goes to sleep.




When a customer enters the barbershop,
they sit in the waiting room if there is a
seat. Otherwise they stand and wait for
a chair.
                                              If the Barber’s chair is free,
                                             The customer wakes the barber
                                                   and sits in the chair.


When a customer enters the barbershop,
they sit in the waiting room if there is a
seat. Otherwise they stand and wait for
a chair.
The sleeping Barber is a
Consumer-Producer Problem
  producers                     queue                   consumers




producer and consumer threads share the queue and must
lock the resource when inserting or removing items. When the
queue is empty, consumers should block until an item becomes
available. If the queue is full, producers should block until there
is space to insert an item
         The Circular Queue
             Problem …
bufin – points to           We need to make sure that
the next available slot     a producer cannot write data
                            when the buffer is full, otherwise
                            it will write over existing good data.




                            We need to make sure that
  bufout – points to the    a consumer cannot remove data
  next item to be removed   if there is no data there. Otherwise
                            it will get garbage.
                              Circular Queue
                               when accessing the buffer,
                               use a mutex to make sure that
                               another thread is not accessing
                               it at the same time.
                                                            producer code
                bufin – points to                           void put_item(int item)
                the next                                    {
                available slot                                pthread_mutex_lock(&myLock);
                                                               buffer[bufin] = item;
                                                               bufin = (bufin + 1) % BUFSIZE;
                                                               pthread_mutex_unlock(&myLock);
                                                           }




consumer code

     void get_item(int *item)
     {
       pthread_mutex_lock(&myLock);
       *item = buffer[bufout];            bufout – points to the next
       bufout = (bufout + 1) % BUFSIZE;   item to be removed
       pthread_mutex_unlock(&myLock);
     }
           Initializing the
            semaphores
                 Un-named semaphores
                            declare the semaphore variables.
                            items manages the number of items
                                   in the buffer ready for removal
                            slots manages the number of buffer
                                  positions available for data
sem_t items;
sem_t slots;
                        The initial value. For items
…                       start at 0, no items to remove


sem_init(&items, 0, 0);
sem_init(&slots, 0, BUFSIZE);      slots starts at BUFSIZE,
                                    … all slots are available


      when this parameter is 0
      the semaphore is local to
      this process and can only be used
      by threads in this process.
            The Producer Thread

                                              SUMSIZE is a globally defined
static void *producer(void *arg1)             constant that controls the number
{                                             of items processed.
  int i;

    for (i = 1; i <= SUMSIZE; i++ )
                                   the producer thread tries to get a buffer slot.
    {                              If none is available (slots == 0), then the
      sem_wait(&slots);            thread waits until one becomes available. When
      put_item(i*i);               a slot becomes available ( slots > 0 ) sem_wait
      sem_post(&items);            decrements slots and the thread proceeds
                                   to execute.
    }
    return NULL;                  after putting the data in the buffer,
}                                     the producer thread increments the
                                      count of removable items by calling
                                      sem_post(&items), indicating that there
                                      is more data available for removal..
           The Consumer Thread

static void *consumer(void *arg2)
{
  int i, myItem;

    for (i = 1; i <= SUMSIZE; i++ )
    {                             the consumer thread tries to get available data.
                                  If none is available (items == 0), then the
      sem_wait(&items);
                                  thread waits until one becomes available. When
      get_item(&myItem);          data becomes available ( items > 0 ), the
      sem_post(&slots);           the sem_wait call decrements the count and
      sum += myitem;              the thread continues to execute..
    }
    return NULL;                     after getting the data from the buffer,
                                         the consumer thread increments the
}                                        count of open buffer slots by calling
                                         sem_post(&slots).
                                  Question?
             Will this program work correctly if there is more than one
                                 consumer thread?


                                            static void *consumer(void *arg2)
                                            {
                                              int i, myItem;
              No!
All consumer threads will try                   for (i = 1; i <= SUMSIZE; i++ )
to process SUMSIZE items. Eventually            {
the consumer threads will block, since            sem_wait(&items);
the producer thread only produced                 get_item(&myItem);
SUMSIZE items,                                    sem_post(&slots);
                                                  sum += myitem;
                                                }
                                                return NULL;
                                            }
      The Producer Driven
            Problem

The problem is that once a consumer thread blocks
on a semaphore, there is no way to unblock the consumer
Except by incrementing the semaphore with a sem_post.
A producer who is finished cannot do a sem_post without
making the consumers think that there is an item available
to be removed.

								
To top
;