/* Dempsey-Jensen, Brychan, 14299890, Assignment 2, 159.341 */ /* Use semaphores to prevent race conditions in a concurrent lift-simulator */ // ************* // Note: // Uncommenting this definition will print populations & the number of people next to the building // #define POPULATIONS // -------------------------------------------------- // --- 159.341 Assignment 2 - Lift Simulator --- // -------------------------------------------------- // Written by M. J. Johnson // Updated by D. P. Playne - 2019 #include #include #include #include "lift.h" // -------------------------------------------------- // Define Problem Size // -------------------------------------------------- #define NLIFTS 4 // The number of lifts in the building #define NFLOORS 20 // The number of floors in the building #define NPEOPLE 20 // The number of people in the building #define MAXNOINLIFT 10 // Maximum number of people in a lift // -------------------------------------------------- // Define delay times (in milliseconds) // -------------------------------------------------- //#define SUPERSLOW //#define SLOW //#define FAST #define SUPERFAST // Original FAST #if defined(SUPERSLOW) #define LIFTSPEED 500 // The time it takes for the lift to move one floor #define GETINSPEED 250 // The time it takes to get into the lift #define GETOUTSPEED 250 // The time it takes to get out of the lift #define PEOPLESPEED 10000 // The maximum time a person spends on a floor #elif defined(SLOW) #define LIFTSPEED 250 // The time it takes for the lift to move one floor #define GETINSPEED 100 // The time it takes to get into the lift #define GETOUTSPEED 100 // The time it takes to get out of the lift #define PEOPLESPEED 100 // The maximum time a person spends on a floor #elif defined(FAST) #define LIFTSPEED 25 // The time it takes for the lift to move one floor #define GETINSPEED 10 // The time it takes to get into the lift #define GETOUTSPEED 10 // The time it takes to get out of the lift #define PEOPLESPEED 10 // The maximum time a person spends on a floor #elif defined(SUPERFAST) #define LIFTSPEED 0 // The time it takes for the lift to move one floor #define GETINSPEED 0 // The time it takes to get into the lift #define GETOUTSPEED 0 // The time it takes to get out of the lift #define PEOPLESPEED 1 // The maximum time a person spends on a floor #endif // -------------------------------------------------- // Define lift directions (UP/DOWN) // -------------------------------------------------- #define UP 1 #define DOWN -1 // Define the lower-case true so that this compiles directly in Windows (GCC 9.2) #if defined(_WIN32) || defined(WIN32) #define true TRUE #endif // -------------------------------------------------- // Information about a floor in the building // -------------------------------------------------- typedef struct { int waitingtogoup; // The number of people waiting to go up int waitingtogodown; // The number of people waiting to go down semaphore up_arrow; // People going up wait on this semaphore down_arrow; // People going down wait on this // People can't all press a button at the same time semaphore queueInteraction; } floor_info; // -------------------------------------------------- // Information about a lift // -------------------------------------------------- typedef struct { int no; // The lift number (id) int position; // The floor it is on int direction; // Which direction it is going (UP/DOWN) int peopleinlift; // The number of people in the lift int stops[NFLOORS]; // How many people are going to each floor semaphore stopsem[NFLOORS]; // People in the lift wait on one of these // control whether the lift is currently adding someone semaphore addToStop; } lift_info; // -------------------------------------------------- // Some global variables // -------------------------------------------------- floor_info floors[NFLOORS]; // keep track of the current target lift on the floor (and a semaphore to prevent this changing prematurely) lift_info *targetLift[NFLOORS]; semaphore targetLiftSelect[NFLOORS]; // Bind all printing to this semaphore, so that the target position can't be changed before printing. semaphore printSemaphore; // If printing people counts, int array and semaphore to keep track of them #ifdef POPULATIONS semaphore updateFloorCount; int floorCount[NFLOORS]; // count of times since value was zero, used to help find a stuck person int floorCountZeroInc[NFLOORS]; // -------------------------------------------------- // Prints a string, with a delay - but it is zero ms // -------------------------------------------------- void print_at_xy_fast(int x, int y, const char *s) { semaphore_wait(&printSemaphore); gotoxy(x, y); Sleep(0); printf("%s", s); semaphore_signal(&printSemaphore); } #endif // -------------------------------------------------- // Print a string on the screen at position (x,y) // -------------------------------------------------- void print_at_xy(int x, int y, const char *s) { // Lock our print to just one thread (stops x,y from changing) semaphore_wait(&printSemaphore); // Move cursor to (x,y) gotoxy(x, y); // Slow things down Sleep(1); // Print the string printf("%s", s); // Move cursor out of the way gotoxy(42, NFLOORS + 2); semaphore_signal(&printSemaphore); } // -------------------------------------------------- // Function for a lift to pick people waiting on a // floor going in a certain direction // -------------------------------------------------- void get_into_lift(lift_info *lift, int direction) { // Local variables int *waiting; semaphore *s; // Check lift direction if (direction == UP) { // Use up_arrow semaphore s = &floors[lift->position].up_arrow; // Number of people waiting to go up waiting = &floors[lift->position].waitingtogoup; } else { // Use down_arrow semaphore s = &floors[lift->position].down_arrow; // Number of people waiting to go down waiting = &floors[lift->position].waitingtogodown; } // Waiting can begin to be decremented while another thread enters the loop. Therefore we // need to prevent an attempt to remove someone who has already been removed while (*waiting) { // Check if lift is empty if (lift->peopleinlift == 0) { // Set the direction of the lift lift->direction = direction; } // Prevent other threads from evaluating the conditional until waiting is decremented semaphore_wait(&floors[lift->position].queueInteraction); if (lift->peopleinlift < MAXNOINLIFT && *waiting) { // Add person to the lift lift->peopleinlift++; print_at_xy(NLIFTS * 4 + floors[lift->position].waitingtogodown + floors[lift->position].waitingtogoup, NFLOORS - lift->position, " "); (*waiting)--; semaphore_signal(&floors[lift->position].queueInteraction); // Wait for person to get into lift Sleep(GETINSPEED); // Wait to set the current lift for the floor as our current lift. We maintain this lock, // while the person whom is later signalled will relase it semaphore_wait(&targetLiftSelect[lift->position]); targetLift[lift->position] = lift; // lock the addtostop, to prevent the lift leaving without the passenger semaphore_wait(&lift->addToStop); // Finally, "open the doors and let someone in" semaphore_signal(s); } else { // There is no-one else waiting; so release our lock semaphore_signal(&floors[lift->position].queueInteraction); break; } } } // -------------------------------------------------- // Function for the Lift Threads // -------------------------------------------------- void *lift_thread(void *p) { // Local variables lift_info lift; int no = (long long)p; int i; // Set up Lift lift.no = no; // Set lift number lift.position = 0; // Lift starts on ground floor lift.direction = UP; // Lift starts going up lift.peopleinlift = 0; // Lift starts empty // A lift can load a single person at a time semaphore_create(&lift.addToStop, 1); for (i = 0; i < NFLOORS; i++) { lift.stops[i] = 0; // No passengers waiting semaphore_create(&lift.stopsem[i], 0); // Initialise semaphores } // Randomise lift randomise(); // Wait for random start up time (up to a second) Sleep(rnd(1000)); // Loop forever while (true) { // Print current position of the lift print_at_xy(no * 4 + 1, NFLOORS - lift.position, lf); // Wait for a while Sleep(LIFTSPEED); // Print the number of people in the lift #ifdef POPULATIONS char str[12]; sprintf(str, "%d", lift.peopleinlift); print_at_xy_fast(no * 4 + 1, NFLOORS + 5, " "); print_at_xy_fast(no * 4 + 1, NFLOORS + 5, str); #endif // Drop off passengers on this floor while (lift.stops[lift.position] != 0) { // These are sequential operations on just one thread (this lift), so don't need protecting here - // The passenger is told to get out and then does work on the floor // One less passenger in lift lift.peopleinlift--; // One less waiting to get off at this floor lift.stops[lift.position]--; // Wait for exit lift delay Sleep(GETOUTSPEED); // Signal passenger to leave lift semaphore_signal(&lift.stopsem[lift.position]); // Check if that was the last passenger waiting for this floor if (!lift.stops[lift.position]) { // Clear the "-" print_at_xy(no * 4 + 1 + 2, NFLOORS - lift.position, " "); } } // Check if lift is going up or is empty if (lift.direction == UP || !lift.peopleinlift) { // Pick up passengers waiting to go up get_into_lift(&lift, UP); } // Check if lift is going down or is empty if (lift.direction == DOWN || !lift.peopleinlift) { // Pick up passengers waiting to go down get_into_lift(&lift, DOWN); } // Erase lift from screen print_at_xy(no * 4 + 1, NFLOORS - lift.position, (lift.direction + 1 ? " " : lc)); // Move lift, but make sure it doesn't move before someone is fully loaded // this really could go anywhere, but it fits the metaphor to wrap it here (doesn't have to wrap anything) semaphore_wait(&lift.addToStop); lift.position += lift.direction; semaphore_signal(&lift.addToStop); // Check if lift is at top or bottom if (lift.position == 0 || lift.position == NFLOORS - 1) { // Change lift direction lift.direction = -lift.direction; } } return NULL; } // -------------------------------------------------- // Function for the Person Threads // -------------------------------------------------- void *person_thread(void *p) { // Local variables int from = 0, to; // Start on the ground floor lift_info *lift; // Randomise randomise(); // Stay in the building forever while (true) { // Work for a while Sleep(rnd(PEOPLESPEED)); do { // Randomly pick another floor to go to to = rnd(NFLOORS); } while (to == from); semaphore *s; // Check which direction the person is going (UP/DOWN) if (to > from) { // One more person waiting to go up // Only one can press the button at a time. Ensure the person is printed to the console before releasing the // semaphore so that there are no ghosts left in the console semaphore_wait(&floors[from].queueInteraction); floors[from].waitingtogoup++; print_at_xy(NLIFTS * 4 + floors[from].waitingtogoup + floors[from].waitingtogodown, NFLOORS - from, pr); semaphore_signal(&floors[from].queueInteraction); s = &floors[from].up_arrow; // Wait for a lift to arrive (going up) } else { semaphore_wait(&floors[from].queueInteraction); // One more person waiting to go down floors[from].waitingtogodown++; print_at_xy(NLIFTS * 4 + floors[from].waitingtogoup + floors[from].waitingtogodown, NFLOORS - from, pr); semaphore_signal(&floors[from].queueInteraction); s = &floors[from].down_arrow; // Wait for a lift to arrive (going down) } // We've pressed our button // waiting for a lift's doors to open semaphore_wait(s); // Which lift we are getting into. The targetLift has locked the variable, so once we have our reference, lift = targetLift[from]; // Realease the targetLift ASAP so another lift can load someone semaphore_signal(&targetLiftSelect[lift->position]); // GetIntoLift() continues its loop, from here (this passenger is signalled to enter; so we definitely need to hold a lock around // this value) // When GetIntoLift() decides that a passenger should enter, it obtains a lock of addToStop. // We release that lock here. Finally, once all passengers have been picked up and the lift decides to move on, if this lock is // still held, then the lift wont change floor (protecting against unloading issues) // We could do this by extending the targetLiftSelect semaphore through the same condition, but that would affect the performance of all lifts on this floor lift->stops[to]++; if (lift->stops[to] == 1) { // Print light for destination print_at_xy(lift->no * 4 + 1 + 2, NFLOORS - to, "-"); } // Person are fully added semaphore_signal(&lift->addToStop); semaphore_wait(&lift->stopsem[to]); // Wait until we are at the right floor #ifdef POPULATIONS // Prints the current location of all people in a line below the building semaphore_wait(&updateFloorCount); floorCount[to]++; floorCount[from]--; semaphore_signal(&updateFloorCount); // Print the current person's floor char str[3]; sprintf(str, "%d", to); print_at_xy_fast((long long)p * 4 + 1, NFLOORS + 8, " "); print_at_xy_fast((long long)p * 4 + 1, NFLOORS + 8, str); #endif // Finally, exit the lift from = to; } return NULL; } // -------------------------------------------------- // Print the building on the screen // -------------------------------------------------- void printbuilding(void) { // Local variables int l, f; // Clear Screen system(clear_screen); // Print the whole building before anything else // Print Roof printf("%s", tl); for (l = 0; l < NLIFTS - 1; l++) { printf("%s%s%s%s", hl, td, hl, td); } printf("%s%s%s%s\n", hl, td, hl, tr); // Print Floors and Lifts for (f = NFLOORS - 1; f >= 0; f--) { for (l = 0; l < NLIFTS; l++) { printf("%s%s%s ", vl, lc, vl); if (l == NLIFTS - 1) { printf("%s\n", vl); } } } // Print Ground printf("%s", bl); for (l = 0; l < NLIFTS - 1; l++) { printf("%s%s%s%s", hl, tu, hl, tu); } printf("%s%s%s%s\n", hl, tu, hl, br); // Print Message printf("Lift Simulation - Press CTRL-Break to exit\n"); #ifdef POPULATIONS gotoxy(0, NFLOORS + 7); printf("People\'s Destination Floors:"); gotoxy(0, NFLOORS + 4); printf("Lift Occupants:"); gotoxy(4 * NLIFTS + NPEOPLE + 5, 0); printf("Number of People on Floor:"); #endif // Ensure buffer is written fflush(stdout); // Once the building has been printed, then we can those waiting to print that they may do so semaphore_signal(&printSemaphore); } #ifdef POPULATIONS // -------------------------------------------------- // Iterate through the floors and update the queue size // -------------------------------------------------- void *update_floor_counts(void *p) { int i; int k; // Iterations since the count on the floor was zero char str2[18] = {';',' ','\t', 'i', 't', 'e', 'r', 's', ' ', 's', 'i', 'n', 'c', 'e', ' ', '0', ':', ' '}; while (true) { for (i = -1; i < NFLOORS; i++) { char str[48]; char str3[12]; if (i != -1) { if (floorCount[i] != 0){ floorCountZeroInc[i]++; } else{ floorCountZeroInc[i] = 0; } sprintf(str, "%d", floorCount[i]); sprintf(str3, "%d", floorCountZeroInc[i]); strcat(str, str2); strcat(str, str3); print_at_xy_fast(4 * NLIFTS + NPEOPLE + 5, NFLOORS - i, " "); print_at_xy_fast(4 * NLIFTS + NPEOPLE + 5, NFLOORS - i, str); } else { int ttl = 0; for (k = 0; k < NFLOORS; k++) { ttl += floorCount[k]; } sprintf(str, "%d", ttl); if (ttl != NPEOPLE) { break; } print_at_xy_fast(4 * NLIFTS + NPEOPLE + 5, NFLOORS - i, str); } } } //Sleep(0); } #endif // -------------------------------------------------- // Main starts the threads and then waits. // -------------------------------------------------- int main() { // Intro stage: printf("|------------------------------------------|\n"); printf("| 159.341 2021 Semester 1, Assignment 2 |\n"); printf("| Submitted by Brychan Dempsey, 14299890 |\n"); printf("|------------------------------------------|\n"); printf("\nPress \'enter\' to begin."); char c; do { c = getchar(); } while (!(c == 10 || c == 12)); // Char is not \r or \n // Local variables unsigned long i; // init the print semaphore semaphore_create(&printSemaphore, 0); // Initialise Building for (i = 0; i < NFLOORS; i++) { floors[i].waitingtogoup = 0; floors[i].waitingtogodown = 0; semaphore_create(&floors[i].up_arrow, 0); semaphore_create(&floors[i].down_arrow, 0); semaphore_create(&floors[i].queueInteraction, 1); semaphore_create(&targetLiftSelect[i], 1); #ifdef POPULATIONS if (i == 0) { semaphore_create(&updateFloorCount, 1); floorCount[0] = NPEOPLE; floorCountZeroInc[0] = 0; } else { floorCount[i] = 0; floorCountZeroInc[i] = 0; } #endif } // Print Building printbuilding(); // Create Lifts for (i = 0; i < NLIFTS; i++) { // Create Lift Thread create_thread(lift_thread, (void *)i); } // Create People for (i = 0; i < NPEOPLE; i++) { // Create Person Thread create_thread(person_thread, (void *)i); } #ifdef POPULATIONS create_thread(update_floor_counts, (void *)i); #endif // Go to sleep for 86400 seconds (one day) Sleep(86400000ULL); }