/*
    Erik Safford
    Producer-Consumer Problem with Threads/Semaphores
    Spring 2020
*/
//compile with -pthread (threads) -lpthread -lrt (semaphores)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdbool.h>
#include <signal.h>

//Number of spaces within buffer_item
#define BUFFER_SIZE 5

//Thread mutex for critical sections of inserting/removing from buffer
pthread_mutex_t mutex;

//Process semaphores for telling if the buffer is empty/full
sem_t empty, full;

//Buffer for producers and consumers to access
typedef int bufferItem;
bufferItem buffer[BUFFER_SIZE];
int bufferIndex;

//Flag to catch when Ctrl-c is entered or sleepTime expires, causing all producers/consumers to exit
bool interruptFlag = false;

//Signal handler to catch Ctrl-c (SIGINT), sets interruptFlag to true
void handleCtrlC() {
    interruptFlag = true;
    printf(" Ctrl-c received, attempting to exit...\n");
}

//Insert item into the buffer
int insertItem(bufferItem item) {
    //return 0 if successful, otherwise return -1 indicating error condition
    if(bufferIndex < BUFFER_SIZE) {
        buffer[bufferIndex] = item;
        bufferIndex++;
        return(0);
    }
    else {
        return(-1);
    }
}

//Remove an object from buffer placing it in item
int removeItem() {
    //return 0 if successful, otherwise return -1 indicating error condition
    bufferItem item;
    if(bufferIndex > 0) {
        bufferIndex--;
        item = buffer[bufferIndex];
        return(0);
    }
    else {
        return(-1);
    }
}

//Producer sleeps for random time, then inserts random number item into buffer
void *producerFunc(void *producerNumber) {
    //Seed random
    srand(time(0) % 10);

    //Attempt to produce until interruptFlag is set to true
    while(!interruptFlag) {
        //Sleep for random time
        sleep(rand() % 10);

        //Get random item value to enter into buffer
        bufferItem item = rand() % 50;

        //Decrement empty semaphore, making it so there is one less available space in buffer for other producers
        if(sem_wait(&empty) != 0) {
            perror("Producer sem_wait error");
        }

        //Lock mutex, only one thread can get past
        if(pthread_mutex_lock(&mutex) != 0) {
            perror("Producer pthread_mutex_lock error");
        }  
        
        //Insert item into buffer
        int ret = insertItem(item);
        if(ret == 0) {
            printf("Producer thread %d produced %d\n", *(int *)producerNumber, item);
        }
        else if(ret == -1) {
            printf("Full buffer\n");
        }

        //Unlock mutex, other threads can now use
        if(pthread_mutex_unlock(&mutex) != 0) {
            perror("Producer pthread_mutex_unlock error");
        } 

        //Increment full semaphore, signaling consumer that there is an item to be consumed
        if(sem_post(&full) != 0) {
            perror("Producer sem_post error");
        }
    }
    return(NULL);
}

//Consumer sleeps for random time, then consumes number item out of the buffer
void *consumerFunc(void *consumerNumber) {
    //Seed random
    srand(time(0) % 10);

    //Attempt to consume until interruptFlag is set to true
    while(!interruptFlag) {
        //Sleep for random time
        sleep(rand()%10);
  
        //Decrement full semaphore, signaling other consumers to wait
        if(sem_wait(&full) != 0) {
            perror("Consumer sem_wait error");
        }

        //Lock mutex, only one thread can get past
        if(pthread_mutex_lock(&mutex) != 0) {
            perror("Consumer pthread_mutex_lock error");
        }
 
        //Remove item from buffer
        int ret = removeItem();
        if(ret == 0) {
            printf("Consumer thread %d consumed %d\n", *(int *)consumerNumber, buffer[bufferIndex]);
        }
        else if(ret == -1) {
            printf("Empty buffer\n");
        }

        //Unlock thread mutex, other threads can now use
        if(pthread_mutex_unlock(&mutex) != 0) {
            perror("Consumer pthread_mutex_unlock error");
        }

        //Increment empty semaphore, making it so there is one more available space in buffer for producers
        if(sem_post(&empty) != 0) {
            perror("Consumer sem_post error");
        } 
    }
    return(NULL);
}

int main(int argc, char **argv) {
    //Check for proper number of command line args
    if(argc != 4) {
        printf("./pc howLongToSleep numberOfProducers numberOfConsumers\n");
        exit(1);
    }

    /* 1. Get command line arguments argv[1], argv[2], argv[3] */

    char *ptr;
    //Get how long to sleep before terminating
    long sleepTime;
    if((sleepTime = strtol(argv[1], &ptr, 10)) == 0) {
        fprintf(stderr, "sleepTime must be a postive numeric value\n");
        exit(1);
    }
    //Get how many producer threads to create
    long numProducers;
    if((numProducers = strtol(argv[2], &ptr, 10)) == 0) {
        fprintf(stderr, "numProducers must be a postive numeric value\n");
        exit(1);
    }
    //Get how many consumer threads to create
    long numConsumers;
    if((numConsumers = strtol(argv[3], &ptr, 10)) == 0) {
        fprintf(stderr, "numConsumers must be a postive numeric value\n");
        exit(1);
    }
    printf("Waiting for %ld seconds before exiting, with %ld producers and %ld consumers...\n", sleepTime, numProducers, numConsumers);

    /* 2. Initialize the buffer, threads, thread mutex, and process semaphores */

    //bufferIndex starts at 0 (empty)
    bufferIndex = 0;
    //Create pthread array to help create numProducers producers and numConsumers consumers
    pthread_t threads[numProducers + numConsumers];
    //Initialize thread mutex with defaults
    if(pthread_mutex_init(&mutex, NULL) != 0) {
        perror("main pthread_mutex_init error");
    }
    //empty semaphore initialized to BUFFER_SIZE so numProducers producers can add BUFFER_SIZE elements to buffer
    if(sem_init(&empty, 0, BUFFER_SIZE) != 0) {
        perror("main sem_init error (empty)");
    }
    //full semaphore initialized to 0 so producer can increment once buffer is ready to be consumed by consumer
    if(sem_init(&full, 0, 0) != 0) {
        perror("main sem_init error (full)");
    }

    /* 3. Create producer thread(s) */
    /* 4. Create consumer thread(s) */

    //Create int array to keep track of producer and consumer threads
    int threadNumber[numProducers + numConsumers];
    //Create producer and consumer threads
    for(int i = 0; i < numProducers + numConsumers; i++) {
        threadNumber[i] = i;
        if(i < numProducers) {
            if(pthread_create(&threads[i], NULL, producerFunc, (void *)&threadNumber[i]) != 0) {
                perror("main pthread_create error (producer)");
            }
        }
        else {
            if(pthread_create(&threads[i], NULL, consumerFunc, (void *)&threadNumber[i]) != 0) {
                perror("main pthread_create error (consumer)");
            }
        }
    }
	
    /* 5. Set the signal handler and sleep for a sleepTime period of time */

    if(signal(SIGINT, handleCtrlC) == SIG_ERR) {
        perror("main signal error");
    }
    sleep(sleepTime);
    
    /* 6. Upon awakening, tell threads to cancel, join, and then exit the application */

    //Set interrupt flag, which breaks consumers/producers from their infinite loops
    interruptFlag = true;
    //Tell all threads to cancel (deffered cancellation)
    for(int i = 0; i < numProducers + numConsumers; i++) {
        if(pthread_cancel(threads[i]) != 0) {
            perror("main pthread_cancel error");
        } 
    }
    //Wait until all threads have exited (joined)
    for(int i = 0; i < numProducers + numConsumers; i++) {
        if(pthread_join(threads[i], NULL) != 0) {
            perror("main pthread_join error");
        } 
    }
    printf("All threads joined, destroying mutexes/semaphores...\n");
    //Destroy semaphores and mutexes
    if(sem_destroy(&empty) != 0) {
        perror("main sem_destroy error (empty)");
    }
    if(sem_destroy(&full) != 0) {
        perror("main sem_destroy error (full)");
    }
    if(pthread_mutex_destroy(&mutex) != 0) {
        perror("main pthread_mutex_destroy error");
    }
    return(0);
} 

C Language online compiler

Write, Run & Share C Language code online using OneCompiler's C online compiler for free. It's one of the robust, feature-rich online compilers for C language, running the latest C version which is C18. Getting started with the OneCompiler's C editor is really simple and pretty fast. The editor shows sample boilerplate code when you choose language as 'C' and start coding!

Read inputs from stdin

OneCompiler's C online editor supports stdin and users can give inputs to programs using the STDIN textbox under the I/O tab. Following is a sample C program which takes name as input and print your name with hello.

#include <stdio.h>
int main()
{
    char name[50];
    printf("Enter name:");
    scanf("%s", name);
    printf("Hello %s \n" , name );
    return 0;
    
}

About C

C language is one of the most popular general-purpose programming language developed by Dennis Ritchie at Bell laboratories for UNIX operating system. The initial release of C Language was in the year 1972. Most of the desktop operating systems are written in C Language.

Key features:

  • Structured Programming
  • Popular system programming language
  • UNIX, MySQL and Oracle are completely written in C.
  • Supports variety of platforms
  • Efficient and also handle low-level activities.
  • As fast as assembly language and hence used as system development language.

Syntax help

Loops

1. If-Else:

When ever you want to perform a set of operations based on a condition if-else is used.

if(conditional-expression) {
   // code
} else {
   // code
}

You can also use if-else for nested Ifs and if-else-if ladder when multiple conditions are to be performed on a single variable.

2. Switch:

Switch is an alternative to if-else-if ladder.

switch(conditional-expression) {    
case value1:    
 // code    
 break;  // optional  
case value2:    
 // code    
 break;  // optional  
...    
    
default:     
 // code to be executed when all the above cases are not matched;    
} 

3. For:

For loop is used to iterate a set of statements based on a condition.

for(Initialization; Condition; Increment/decrement){  
  // code  
} 

4. While:

While is also used to iterate a set of statements based on a condition. Usually while is preferred when number of iterations are not known in advance.

while(condition) {  
 // code 
}  

5. Do-While:

Do-while is also used to iterate a set of statements based on a condition. It is mostly used when you need to execute the statements atleast once.

do {
  // code 
} while (condition); 

Arrays

Array is a collection of similar data which is stored in continuous memory addresses. Array values can be fetched using index. Index starts from 0 to size-1.

Syntax

One dimentional Array:

data-type array-name[size];

Two dimensional array:

data-type array-name[size][size];

Functions

Function is a sub-routine which contains set of statements. Usually functions are written when multiple calls are required to same set of statements which increases re-usuability and modularity.

Two types of functions are present in C

  1. Library Functions:

Library functions are the in-built functions which are declared in header files like printf(),scanf(),puts(),gets() etc.,

  1. User defined functions:

User defined functions are the ones which are written by the programmer based on the requirement.

How to declare a Function

return_type function_name(parameters);

How to call a Function

function_name (parameters)

How to define a Function

return_type function_name(parameters) {  
  //code
}