159.341_Assignment_2/assignment2_handout.c

604 lines
18 KiB
C

/* 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 <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#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);
// flush the stream and release the semaphore
//fflush(stdout);
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);
}