Week 11 Tutorial Questions

Objectives

Code Review

This week the code review is for file_modes.c. The reviewees should give a brief description of their code, and the class should ask questions, comment on the quality of the code, and suggest improvements. Each review should take about 10 minutes.

Code reviews should take place in the second hour of tutorials for the week

Tutorial Questions

  1. What is the difference between concurrency and parallelism?

  2. How can we make our programs concurrent in C?

  3. What are the benefits and disadvantages of executing processes concurrently?

  4. The following C program attempts to say hello from another thread:

    #include <stdio.h>
    #include <pthread.h>
    
    void *thread_run(void *data) {
        printf("Hello from thread!\n");
    
        return NULL;
    }
    
    int main(void) {
        pthread_t thread;
        pthread_create(
            &thread,    // the pthread_t handle that will represent this thread
            NULL,       // thread-attributes -- we usually just leave this NULL
            thread_run, // the function that the thread should start executing
            NULL        // data we want to pass to the thread -- this will be
                        // given in the `void *data` argument above
        );
    
        return 0;
    }
    

    However, when running this program after compiling with gcc, the thread doesn't say hello.

    gcc -pthread program.c -o program
    ./program
    ./program
    ./program
    

    Why does our program exhibit such behaviour?

    How can we fix it?

  5. Write a C program that creates a thread that infinitely prints some message provided by main (eg. "Hello\n"), while the main (default) thread infinitely prints a different message (eg. "there!\n").

  6. Consider the following code

    #include <pthread.h> // for pthread_create, pthread_join, pthread_exit
    #include <stdio.h>   
    #include <stdlib.h>  
    
    #define N 4
    
    void * helloThread(void * arg){
        int i = * (int *) arg;
        printf("Hello from Thread %d\n",i);
        return NULL;
    }
    
    int main(void){
        pthread_t tid[N];
        int i;
    
        for(i=0; i < N; i++){
            if( pthread_create(&tid[i], NULL, helloThread, &i) != 0){
                  fprintf(stderr, "\n ERROR creating thread %d",i);
                  exit(1);
            };
        }
    
        for(i=0; i< N; i++){
           if (pthread_join(tid[i], NULL) != 0){
                 fprintf(stderr, "\n ERROR joining thread");
                 exit(1);
           }
        }
        exit(0);
    }
    

    Explain how I could possibly get the following output.

    ./program
    Hello from Thread 1
    Hello from Thread 2
    Hello from Thread 1
    Hello from Thread 3
    ./program
    Hello from Thread 1
    Hello from Thread 2
    Hello from Thread 3
    Hello from Thread 2
    

    Fix it to make sure the following lines are printed out every time the program is run. The lines can be printed out in any order

    Hello from Thread 0
    Hello from Thread 1
    Hello from Thread 2
    Hello from Thread 3
    
  7. Concurrency can allow our programs to perform certain actions simultaneously that were previously tricky for us to do as CP1521 students.

    For example, with our current C knowledge, we cannot execute any code while waiting for input (with, for example, scanf, fgets, etc.).

    Write a C program that creates a thread which infinitely prints the message "feed me input!\n" once per second (sleep(3)), while the main (default) thread continuously reads in lines of input, and prints those lines back out to stdout with the prefix: "you entered: ".

  8. The following C program attempts to increment a global variable in two different threads, 5000 times each.

    #include <stdio.h>
    #include <pthread.h>
    
    int global_total = 0;
    
    void *add_5000_to_counter(void *data) {
        for (int i = 0; i < 5000; i++) {
            // sleep for 1 nanosecond
            nanosleep (&(struct timespec){.tv_nsec = 1}, NULL);
            
            // increment the global total by 1
            global_total++;
        }
    
        return NULL;
    }
    
    int main(void) {
        pthread_t thread1;
        pthread_create(&thread1, NULL, add_5000_to_counter, NULL);
    
        pthread_t thread2;
        pthread_create(&thread2, NULL, add_5000_to_counter, NULL);
    
        pthread_join(thread1, NULL);
        pthread_join(thread2, NULL);
    
        // if program works correctly, should print 10000
        printf("Final total: %d\n", global_total);
    }
    

    Since the global starts at 0, one may reasonably assume the value would total to 10000.

    However, when running this program, it often gives differing values each individual execution:

    dcc -pthread program.c -o program
    ./program
    Final total: 9930
    ./program
    Final total: 9983
    ./program
    Final total: 9994
    ./program
    Final total: 9970
    ./program
    Final total: 10000
    ./program
    Final total: 9996
    ./program
    Final total: 9964
    ./program
    Final total: 9999
    

    Why does our program exhibit such behaviour?

  9. How can we use "mutual exclusion" to fix the previous program?

  10. How can we use atomic types to fix the previous program?

  11. Explain why running the following code can result in deadlock

    #include <stdio.h>
    #include <pthread.h>
    #include <semaphore.h>
    
    int andrews_bank_account1 = 100;
    pthread_mutex_t bank_account1_lock = PTHREAD_MUTEX_INITIALIZER;
    
    int andrews_bank_account2 = 200;
    pthread_mutex_t bank_account2_lock = PTHREAD_MUTEX_INITIALIZER;
    
    // swap values between Andrew's two bank account 100,000 times
    void *swap1(void *argument) {
        for (int i = 0; i < 100000; i++) {
             
            pthread_mutex_lock(&bank_account1_lock);
            pthread_mutex_lock(&bank_account2_lock);
    
            int tmp = andrews_bank_account1;
            andrews_bank_account1 = andrews_bank_account2;
            andrews_bank_account2 = tmp;
    
            pthread_mutex_unlock(&bank_account2_lock);
            pthread_mutex_unlock(&bank_account1_lock);
    
        }
    
        return NULL;
    }
    
    // swap values between Andrew's two bank account 100,000 times
    void *swap2(void *argument) {
        for (int i = 0; i < 100000; i++) {
            pthread_mutex_lock(&bank_account2_lock);
            pthread_mutex_lock(&bank_account1_lock);
    
            int tmp = andrews_bank_account1;
            andrews_bank_account1 = andrews_bank_account2;
            andrews_bank_account2 = tmp;
    
            pthread_mutex_unlock(&bank_account1_lock);
            pthread_mutex_unlock(&bank_account2_lock);
    
        }
    
        return NULL;
    }
    
    int main(void) {
        //create two threads performing almost the same task
       
        pthread_t thread_id1;
        pthread_create(&thread_id1, NULL, swap1, NULL);
    
        pthread_t thread_id2;
        pthread_create(&thread_id2, NULL, swap2, NULL);
    
        // threads will possibly never finish
        // deadlock will likely occur
    
        pthread_join(thread_id1, NULL);
        pthread_join(thread_id2, NULL);
    
        return 0;
    }
    
  12. Write a program where a child process writes to a parent process. The child should send the following array of strings, one by one
    char * messages[] = {"red\n",
                       "yellow\n",
                       "pink\n",
                       "green\n",
                       "purple\n",
                       "orange\n",
                       "blue\n"};
    
    The parent should print out the strings it receives.
  13. Write a program using popen that implements the following using popen :
    grep main file.txt | wc