-1

I have a C program that I have to write that creates 4 threads. These 4 "pirate" threads are supposed to access 1000 pearls from a "cave" (a double with value 1000), taking 10% or 15% out of the "cave." They are supposed to go only one at a time, and the program is supposed to stop when the cave of pearls is empty. The program seems to run okay, but hangs after a few runs of the executable.

Why is this happening?

CODE:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <math.h>

#define NUM_PIRATE_THREADS 4
#define OPEN_SESAME 0
#define OPEN_WATERMELON 1

/* array of threads */
static pthread_t *thread_handles;

/* mutual exlusion lock; mutual exclusion considered in this version */
pthread_mutex_t mutex;

/* pirate thread function */
void *pirate(void* rank);

/* total number of items in resource (pearls in the cave) */
static double cavePearls = 1000.00;

/* amount of pearls pirate thread may take */
static double pearlsToGrab;

/* array to store the number of pearls garnered by each pirate */
int piratesBooty[NUM_PIRATE_THREADS];

/* main function */
int main() {

    /* variable to loop through threads and output pearl consumption */
    long threadIndex;

    /* thread variable */
    long currentThread;

    /* initialize thread_handles array */
    thread_handles = (pthread_t*)malloc(NUM_PIRATE_THREADS*sizeof(pthread_t));

    /* create pirate threads...ARGGG!*/
    for (currentThread = 0; currentThread < NUM_PIRATE_THREADS; ++currentThread) {
        pthread_create(&thread_handles[currentThread], NULL, (void *(*)(void *))pirate, (void*)currentThread);
    }

    /* join pirate threads...ARGGG!*/
    for (currentThread = 0; currentThread < NUM_PIRATE_THREADS; ++currentThread) {
        pthread_join(thread_handles[currentThread], NULL);
    }

    /* update your final cave pearl number to a whole integer value */
    cavePearls = ceil(cavePearls);

    /* display pearl data after thread(s) execution */
    printf("\nPearls left in cave: %d\n", (int)cavePearls);
    for (threadIndex = 0; threadIndex < NUM_PIRATE_THREADS; ++threadIndex){
        printf("Pirate %ld: %d pearls\n", threadIndex, piratesBooty[threadIndex]);
    }

    /* free memory */
    free(thread_handles);

    return 0;
}

void *pirate(void* rank) {

    while(1) { /* continue execution as long as there are still pearls in the cave */
        if (cavePearls == 0) /* cave has been emptied, pirates should stop */
            return 0;

        /* identify which pirate thread you are executing */
        long my_rank = (long)rank;

        /* CRITICAL SECTION LOCKED */
        pthread_mutex_lock(&mutex);

        if (cavePearls) {
            if (my_rank % 2 == OPEN_SESAME) { /* if thread is 0 or 2: "Open, sesame" pirate */
                pearlsToGrab = cavePearls * 0.10; /* "open sesame" = 10% of pearls removed */
                pearlsToGrab = ceil(pearlsToGrab); /* get the ceiling if not an integer value */
                cavePearls = cavePearls - pearlsToGrab; /* update the number of pearls in cave */
                piratesBooty[my_rank] += pearlsToGrab; /* update pirate thread total pearls taken */

            }
            else if (my_rank % 2 == OPEN_WATERMELON){ /* if thread is 1 or 3: "Open, watermelon" pirate */
                pearlsToGrab = cavePearls * 0.15; /* "open watermelon" = 15% of pearls removed */
                pearlsToGrab = ceil(pearlsToGrab); /* get the ceiling if not an integer value */
                cavePearls = cavePearls - pearlsToGrab; /* update the number of pearls in cave */
                piratesBooty[my_rank] += pearlsToGrab; /* update pirate thread total pearls taken */
            }
        } /* end of while-loop */

        /* CRITICAL SECTION UNLOCKED */
        pthread_mutex_unlock(&mutex);

        /* DEBUG SCRIPT
         printf("I am thread %ld, I have %d pearls, I'm taking %d pearls, and \nthere are %d pearls left"
         "in the cave.\n\n", my_rank, piratesBooty[my_rank], (int)pearlsToGrab, (int)cavePearls);
         */

    }
    /* have thread(s) terminate */
    pthread_exit((void*)0);
}

EVERYTHING LOOKS GOOD! Made changes and things look good. Feel free to let me know if you see any errors!

CODE:

/*******************************************************************************
 *******************************************************************************
 Course: Operating Systems
 Student: Douglas Adolph
 Project: 3, Part 2

 -------------------------------------------------------------------------------
 ///////////////////////////////////////////////////////////////////////////////
 IMPORTANT NOTE:
 ///////////////////////////////////////////////////////////////////////////////
 Some of this code borrows directly from material provided by Dr. Burtscher
 in Texas State University's Parallel Programming course, as well as material
 written by the author (Douglas Adolph) for said parallel programming course.
 ///////////////////////////////////////////////////////////////////////////////
 -------------------------------------------------------------------------------

 -------------------------------------------------------------------------------
 ///////////////////////////////////////////////////////////////////////////////
 SUMMARY:
 ///////////////////////////////////////////////////////////////////////////////
 Program illustrates use of threads and mutual exclusion. N "pirates" (threads)
 complete for 1000 pearls in a cave. Passwords are used to be granted a certain
 percentage of the remaining pearls in the cave. Program should terminate when
 all the pearls have been taken.
 ///////////////////////////////////////////////////////////////////////////////
 -------------------------------------------------------------------------------

 -------------------------------------------------------------------------------
 ///////////////////////////////////////////////////////////////////////////////
 PART 1 AND 2 SPECIFICS:
 ///////////////////////////////////////////////////////////////////////////////
 Part 1: implementation with mutual exclusion NOT considered*
 Part 2: implementation with mutual exclusion considered

 *designed to fail during repeated runtime tests: demonstrate need for exclusion
 *sleep() not used
 ///////////////////////////////////////////////////////////////////////////////
 -------------------------------------------------------------------------------

 -------------------------------------------------------------------------------
 ///////////////////////////////////////////////////////////////////////////////
 THREAD ID, THREAD PASSWORD, AND RESOURCE (PEARLS) RETRIEVAL INFORMATION:
 ///////////////////////////////////////////////////////////////////////////////
 Pirate threads ID'd by rank (i.e. 0, 1, 2, ...), and this rank is used to ID
 which password pirate thread will use. However, in the program output, pirate
 threads are labeled alphanumerically (i.e. A, B, C, ...). However, although
 pirate threads in output are labeled alphanumerically to match professor's
 example, some liberty was taken in the final design of the output.

 Note: if there are more than 26 pirate threads, the implementation of the output
       of total pearls gathered by all pirate threads will need to be changed, or
       labels for all pirate threads after Z will not have reasonable labels

 "Open, sesame:" N/2 pirate processes use this password and
                 receive 10% of the remaining pearls

 "Open, watermelon:" N/2 pirate processes use this password and
                     receive 15% of the remaining pearls

 Even-numbered pirate threads will be assigned password "Open, sesame," where
 password string is represented by 1 (*)

 Odd-numbered pirate threads will be assigned password "Open, watermelon," where
 password string is represented by 0 (*)

 (*) via a modulus operation (thread % 2); string PWs/buffers avoided
 ///////////////////////////////////////////////////////////////////////////////
 -------------------------------------------------------------------------------

 -------------------------------------------------------------------------------
 ///////////////////////////////////////////////////////////////////////////////
 NOTES REGARDING TRANSFER AND EXECUTION OF FILE ON TEXAS STATE SERVERS:
 ///////////////////////////////////////////////////////////////////////////////

 (1) transfer to Zeus for testing effected via:

 scp /Users/douglasadolph/Desktop/School/CourseFolders/OperatingSystems/Projects
 /YourProjects/project3_DouglasAdolph/FILE_NAME da1140@zeus.cs.txstate.edu:

 (2) program compiled via:

 gcc p3_part1_DouglasAdolph.c -o p3p1 -lpthread -lm
 gcc p3_part2_DouglasAdolph.c -o p3p2 -lpthread -lm
 ///////////////////////////////////////////////////////////////////////////////
 -------------------------------------------------------------------------------
 *******************************************************************************
 ******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <math.h>

#define NUM_PIRATE_THREADS 4 /* number of pirate threads to run */
#define OPEN_SESAME 0 /* single bit value representation of password */
#define OPEN_WATERMELON 1 /* single bit value representation of password */

/* percent value for "open, sesame" */
const double OPEN_SESAME_PERCENTAGE = 0.10;

/* percent value for "open, watermelon" */
const double OPEN_WATERMELON_PERCENTAGE = 0.15;

/* array of pirate threads */
static pthread_t *thread_handles;

/* mutual exlusion lock; mutual exclusion considered in this version */
pthread_mutex_t mutex;

/* pirate thread function */
void *pirate(void* rank);

/* total number of items in resource (pearls in the cave) */
static double cavePearls = 1000.00;

/* array to store the number of pearls garnered by each pirate */
int piratesBooty[NUM_PIRATE_THREADS];

/* main function */
int main() {

    /* alert user pirate threads are about to begin consuming pearls */
    printf("\nAvast matey, we are a'comin' fer yee pearls!!\n\n");

    /* index variable for pirate threads */
    long threadIndex;

    /* char variable for pirate thread labeling (i.e. 1, 2, 3, ...) */
    char alphaForPirate = 'A';

    /* create and allocate memory for thread_handles array */
    thread_handles = (pthread_t*)malloc(NUM_PIRATE_THREADS*sizeof(pthread_t));

    /* create and run pirate threads...YAR!*/
    for (threadIndex = 0; threadIndex < NUM_PIRATE_THREADS; ++threadIndex) {
        pthread_create(&thread_handles[threadIndex], NULL,
                       pirate, (void*)threadIndex);
    }

    /* join pirate threads...AVAST MATEY!*/
    for (threadIndex = 0; threadIndex < NUM_PIRATE_THREADS; ++threadIndex) {
        pthread_join(thread_handles[threadIndex], NULL);
    }

    /* update your final cave pearl number to a whole integer value */
    cavePearls = ceil(cavePearls);

    /* display pearl data after pirate thread(s) execution */
    printf("\nYar!! The cave be empty!!\n\n");
    for (threadIndex = 0; threadIndex < NUM_PIRATE_THREADS; ++threadIndex){
        printf("Pirate %c got %d pearls\n",
               alphaForPirate, piratesBooty[threadIndex]);
        alphaForPirate++;
    }
    printf("\n");

    /* free memory */
    free(thread_handles);

    return 0;
} /* end of main() */

void *pirate(void* rank) {

    /* amount of pearls pirate thread(s) may take during current entry to cave */
    static double pearlsToGrab = 0;

    /* variables to output pirate thread(s) pearl consumption */
    char alphaForPirate = 'A';
    int piratePercentage;

    /* label pirate thread(s) alphanumerically */
    alphaForPirate += (long)rank;

    while(1) { /* continue execution while pearls remain in cave */
        if (cavePearls < 1) /* cave has been emptied, pirate thread(s) should stop */
            return 0;

        /* identify which pirate thread you are currently executing */
        long my_rank = (long)rank;

        /* if pirate thread is even: "Open, sesame" pirate */
        if (my_rank % 2 == OPEN_SESAME) {

            /* CRITICAL SECTION LOCKED */
            pthread_mutex_lock(&mutex);

            piratePercentage = (OPEN_SESAME_PERCENTAGE * 100);
            pearlsToGrab = cavePearls * OPEN_SESAME_PERCENTAGE;
            pearlsToGrab = ceil(pearlsToGrab);
            cavePearls = cavePearls - pearlsToGrab;
            piratesBooty[my_rank] += pearlsToGrab;

            printf("Pirate %c gets %.0f of the pearls, %d percent of %.0f pearls available in cave\n",
                   alphaForPirate, pearlsToGrab, piratePercentage, (cavePearls + pearlsToGrab));

            /* CRITICAL SECTION UNLOCKED */
            pthread_mutex_unlock(&mutex);
        }
        /* if pirate thread is odd: "Open, watermelon" pirate */
        else if (my_rank % 2 == OPEN_WATERMELON){

            /* CRITICAL SECTION LOCKED */
            pthread_mutex_lock(&mutex);

            piratePercentage = (OPEN_WATERMELON_PERCENTAGE * 100);
            pearlsToGrab = cavePearls * OPEN_WATERMELON_PERCENTAGE;
            pearlsToGrab = ceil(pearlsToGrab);
            cavePearls = cavePearls - pearlsToGrab;
            piratesBooty[my_rank] += pearlsToGrab;

            printf("Pirate %c gets %.0f of the pearls, %d percent of %.0f pearls available in cave\n",
                   alphaForPirate, pearlsToGrab, piratePercentage, (cavePearls + pearlsToGrab));

            /* CRITICAL SECTION UNLOCKED */
            pthread_mutex_unlock(&mutex);
        }

        /* make pirate thread(s) sleep for 2 seconds */
        sleep(2);

    } /* end of while-loop */

    /* have pirate thread(s) terminate */
    pthread_exit((void*)0);
}
  • 1
    `(void *(*)(void *))pirate` <--- what is all that mess in front of `pirate`? As far as I can tell `pirate` matches the function signature that `pthread_create` expects.. just do `pthread_create(&thread_handles[currentThread], NULL, pirate, (void*)currentThread);` – yano Oct 27 '17 at 04:18
  • 2
    also recommend `return 0;` --> `break;` and moving the declaration of `pearlsToGrab` to the `pirate` function. – yano Oct 27 '17 at 04:27
  • 1
    Think about what happens if `pearlsToGrab` is close to but not exactly zero.... – Andrew Henle Oct 27 '17 at 10:21
  • 1
    @AndrewHenle: Nothing special. Except at the temp step before `ceil` is applied (and the mutex is held there, so nothing else can see it), it's an integer. – R.. GitHub STOP HELPING ICE Oct 27 '17 at 14:54

2 Answers2

2

Run it in a debugger until it hangs, then break into it and look at the state. I suspect that for some executions, cavePearls goes negative and your threads just keep going. if (cavePearls == 0) should be `if (cavePearls <= 0)'.

jwdonahue
  • 6,199
  • 2
  • 21
  • 43
  • 1
    @DouglasAdolph As suggested in this answer, be very wary of exact comparisons between floating points (that is, avoid doing it): https://stackoverflow.com/questions/588004/is-floating-point-math-broken – yano Oct 27 '17 at 04:31
  • This is not the problem. The code as written uses `double` just to store integers (in the range where they fit exactly). There is no rounding anywhere except at the step just before `ceil`. – R.. GitHub STOP HELPING ICE Oct 27 '17 at 14:53
  • @R.., Nothing to do with doubles or ints, consider the combinations of 15 and 10 that add up to exactly 100. It's likely that random thread timings cause combinations that don't. – jwdonahue Oct 27 '17 at 16:20
2

At the very least, this line is invalid and causes your program to have undefined behavior:

        if (cavePearls == 0) /* cave has been emptied, pirates should stop */

You cannot access shared mutable data without holding a lock to sequence your access with respect to accesses by other threads.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • Almost certainly not the problem. Even assuming that double can't be read/written atomically, the bits eventually settle to zero or a non-zero value. I agree however that the test should be guarded. – jwdonahue Oct 27 '17 at 04:38