CMPS 3600 Lab 6 - Thread Synchronization

  · POSIX Threads
  · System-V Semaphores
  · Producer/consumer synchronization

Resources

Overview: A consumer/producer problem is very similar to a reader/writer problem. In our program we will have one producer and one consumer. 1. Get the lab sample files into your directory. 2. Copy echo.c to your tvlab6.c 3. Add tvlab6.c to your Makefile. 4. Build and run the program. 5. Move the producer functionality to a function outside of main. This will run as a thread. 6. Build a producer function of your own design. It will be very similar to the consumer function. It will run as a thread. 7. Start the producer and consumer threads running. 8. Add a set of two System-V semaphores to your program. Use your semaphores to synchronize the producing & consuming. 9. Procedure: Each thread will wait for its own semaphore to be available. A wait-for-zero operation will be implemented. The critical code will be executed. Then the other thread's semaphore will be released. A sample of using multiple semaphores in a program can be seen in our own 3600/6/twosems.c program written in class. Odin files to be collected from 3600/6 folder: tvlab6.c Makefile foo poem

Copy all example code for this week into your directory:

    $ cd
    $ cd 3600/6
    $ cp /home/fac/gordon/public_html/3600a/examples/6/* .

Execute the programs and verify that they are working as you would expect. Review the source of each program. You should understand the concepts before starting to code the lab. Your job is to implement producer/consumer problem synchronization for the two threads in echo.c. Linux supports POSIX interface for semaphores, which is considerably more user-friendly than System V, but System V is the de facto standard - most system code uses System V. You will add SysV semaphores to echo.c. Copy echo.c to tvlab6.c:
    $ cp echo.c tvlab6.c

Add an entry for tvlab6.c to your Makefile.

Program echo.c is a bounded buffer problem with a buffer size of one character.
A bounded buffer lets multiple producers and multiple consumers share a single buffer. Producers write data to the buffer and consumers read data from the buffer.
- Producers block if the buffer is full.
- Consumers block if the buffer is empty.

The current code implements mutual exclusion on the char buffer but not synchronization on the threads. The fib(n) calls are in the code to demonstrate what happens if one thread is slightly slower or faster than the other thread. The producer reads one character at a time from 'poem' and writes it to the buffer. The consumer reads one character at a time from the buffer and writes it to stdout. The threads are out of sync. Output currently looks something like this:
    producer thread pid: 27346 tid: 27346
    consumer thread pid: 27346 tid: 27347
    he  ssuuunnn   iiinnn   mmy  hhaanndd
    GGGooonnnnnnaa  t
STEP 1. In echo.c the main thread is also the producer thread. Modify this so that the producer is its own thread with its own thread function. The main thread will create both the producer and the consumer threads. Make sure your code is working with the new thread before continuing. Join your producer thread in main.

STEP 2. Now you will solve the problem of synchronization. Create two SysV semaphores. Use these semaphores to synchronize the two threads so that the producer does not modify the char buffer until the consumer has consumed the char (read the value) and the consumer does not consume the same value twice (e.g., consume from an empty/old buffer). If all goes correctly you should see the poem displayed correctly on the screen. During debugging it is useful to grab information about a semaphore. For help on doing this see sem_ctrl.c

Do not define multiple semids.
Define just one semid, but then specify a set of semaphores in semget().
In your grab[] and release[] arrays, indicate a sem_num of 0 or 1 for the semaphore you are using.

Unlike the Producer/Consumer solution in the last lab (5) that used a single semaphore, use two semaphores for this lab. One semaphore will control the producer and one semaphore will control the consumer. The advantage to two semaphores is that the operations to grab and release are similar but the semaphore that the operation is applied to alternates. The producer and consumer execute their critical section the same number of times inside a loop. To prevent deadlock, both threads will release the other's semaphore before exit. You can ensure that the producer starts first by initializing the producer's semaphore value appropriately.

This section updated to reflect the numbers used during lectures.
Each thread has a loop.
Inside the loops you will grab, then release a semaphore.
Below is thread pseudocode.


           *------------*               *------------*
           *  PRODUCER  *               *  CONSUMER  *
           *------------*               *------------*
                                       
           # producer starts            # consumer blocks
           PSEM.val = 0                 CSEM.val = 1
           # start looping              # start looping
           ploop:                       cloop:
               if DONE                      if DONE
                   goto end                     goto end
               # producer grabs             # consumer grabs
               wait_on_zero                 wait_on_zero
               PSEM.op += 1                 CSEM.op += 1
               {{ critical code }}          {{ critical code }}
               # release consumer           # release producer
               CSEM.op -= 1                 PSEM.op -= 1
           goto ploop                   goto cloop
           end:                         end:

STEP 3. Pass the value for LIMIT in from the the command line. Set LIMIT to a default value of 50 if no argument is passed.

Next, open a logfile named log. Do this in the main thread before starting the threads. The consumer will write to log. Use formatted file streams to make coding a little easier. Formatted file streams: fopen, fprintf, fscanf, fclose.

FILE *fout;  /* needs to be global so threads can see it */

fout = fopen("log", "w");
if (fout == NULL) {
    fprintf(stderr, "error opening output file.\n");
    exit(1);
}

All output from the consumer should go to log instead of to the screen. Close the file after the main thread joins the two threads.

Sample run...
$ ./lab6 61 $ cat log consumer thread pid: 26878 tid: 26879 With the sun in my hand Gonna throw the sun Way across the la
Sample run with strace...
$ strace -e trace=ipc,clone ./lab6 136 semget(0xc02a2de, 2, IPC_CREAT|0666) = 397180933 semctl(397180933, 0, SETVAL, 0x7ffe00000001) = 0 semctl(397180933, 1, SETVAL, 0x7ffe00000000) = 0 clone(child_stack=0x7ffe20abcff0, flags=CLONE_VM|CLONE_FS|CLONE_FILES| CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID| CLONE_CHILD_CLEARTID, parent_tidptr=0x7ffe20abd9d0, tls=0x7ffe20abd700, child_tidptr=0x7ffe20abd9d0) = 10153 clone(child_stack=0x7ffe202bbff0, flags=CLONE_VM|CLONE_FS|CLONE_FILES| CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID| CLONE_CHILD_CLEARTID, parent_tidptr=0x7ffe202bc9d0, tls=0x7ffe202bc700, child_tidptr=0x7ffe202bc9d0) = 10154 semctl(397180933, 0, IPC_RMID, 0x202c0) = 0 $ cat log consumer thread pid: 26878 tid: 26879 With the sun in my hand Gonna throw the sun Way across the land- Cause I'm tired, Tired as I can be So Tired Blues - by Langston Hughes
Odin files to be collected. 3600/6/tvlab6.c 3600/6/Makefile 3600/6/foo 3600/6/poem