Tentative weight for the question types:

The weights of the topics in the test will be scaled to the number of lab classes for the topic, i.e., questions worth about half of the points will deal with course materials from classes 7-9 covering threads, mutexes and condition variables (that is three classes out of six total).

The questions provided below are example questions, not an exhaustive list of possible questions, and the example questions do not cover all course material.


1. Say that you need to pass a double and two ints to a thread routine. Explain how can you do this in pthreads.

2. Explain what is the difference between joining a thread and detaching it.

3. Explain why one has to use mutexes in concurrent programs. Provide a scenario where omitting a mutex causes trouble.

4. Tell what are the two differences between a binary semaphore and a recursive mutex.

5. Provide a scenario in which two threads deadlock so that no other thread can wake them.

6. The method pthread_cond_wait takes as an argument a condition variable and a locked mutex. Explain why one must pass it a locked mutex. You can provide a scenario that would cause trouble if pthread_cond_wait would not require the mutex.

7. Waiting on condition variable is typically called in a while loop. Explain why.


8. Explain how can you learn that a function from POSIX API failed.

9. Provide two ways of telling why a function from POSIX API has failed.

10. List and explain the possible return values of read function in the following code:

int filedes = open("file", O_RDWR);
char letter;
int x = read(filedes, &letter, 1);

11. Explain what happens when you write from several threads to the same file, and explain how the result of the write depend on flags used upon opening the file.

12. Explain the lines below do and what is difference between the two lines:

open( "first", O_RDWR|O_CREAT,        0644);
open("second", O_RDWR|O_CREAT|O_EXCL, 0644);

13. Explain how can you add to a C program custom code to be executed when the program gets interrupted by Ctrl+C.

14. Write how can you tell whether you are in the parent or the child process when you fork.

15. Explain what happens to:

  1. open files
  2. mutexes
  3. threads
  4. memory allocated by malloc
  5. memory mapped by mmap with MAP_SHARED flag

upon a call to fork() function.

16. Tell why "ls" appears twice in the following code:

execlp("ls", "ls", "-xls", 0);

17. Name a function that you can use (prior to fork) to create an unidirectional channel for passing data to child processes.

18. Explain how can you learn that a child process finished.

19. What is the difference between opening a file twice and opening it once and then duplicating the descriptor.

20. What are the limitations of using MAP_ANONYMOUS flag in mmap when creating a shared memory block.

21. Write a line of code that creates a new named semaphore and sets its value to 1.


22. Write two snippets of code and a function. The first snipped of code must run the function in a new thread and pass one double and one int to it. The function must multiply the numbers and make a string (null-terminated char array) out of the result. The second snippet must collect the result string.

23. The code below looks up a number that, when appended to a provided text, will produce a MD5 sum starting with a provided prefix. Fill the gaps to make the code produce exactly 10 results instead of one.

#include <md5.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
// To compile this code, add '-lmd' to compile options.
 
const char *text;
const char *prefix;
#define NO_THREADS 8
 
// writes MD5 sum of 'text' with 'nonce' appended to 'md5'
// returns if MD5 sum starts with 'prefix'
int try(long nonce, char md5[]) {
  char *data;
  int len = asprintf(&data, "%s %lx\n", text, nonce);
  MD5Data((uint8_t *)data, len, md5);
  free(data);
  return !strncmp(prefix, md5, strlen(prefix));
}
 
┌───────────────────────┐
│                       │
└───────────────────────┘
 
void *lookup(void *arg) {
  long i = (intptr_t)arg;
  char md5[MD5_DIGEST_STRING_LENGTH];
  while (1) {
    if (try(i, md5)) {
      ┌───────────────────────┐
      │                       │
      └───────────────────────┘
      printf("%s %lx\n%s\n", text, i, md5);
      ┌───────────────────────┐
      │exit(0);               │
      └───────────────────────┘
    }
    if ((((i += NO_THREADS) / NO_THREADS) % 4096) == 0) {
      ┌───────────────────────┐
      │                       │
      └───────────────────────┘
    }
  }
}
 
int main(int argc, char *argv[]) {
  prefix = argv[1];
  text = argv[2];
  pthread_t t[NO_THREADS];
  for (intptr_t i = 1; i < NO_THREADS; ++i) {
    pthread_create(t + i, NULL, lookup, (void *)i);
  }
  lookup((void *)0);
}

24. The code below produces an incorrect result. Explain why, and propose how to fix the issue.

#include <pthread.h>#include <stdint.h>#include <stdio.h>1void *thread(void *rawArg) {
 2uint64_t arg1 = ((uint64_t *)rawArg)[0];
 3uint64_t arg2 = ((uint64_t *)rawArg)[1];
 4return (void *)(arg1 * arg2);}
  │ 
  │ int main() {
  │   pthread_t threads[18];
 5for (int i = 1; i < 19; ++i) {
 6uint64_t args[] = {i, i + 1};
 7│     pthread_create(&threads[i - 1], NULL, thread, args);}
 8uint64_t sum = 0;
 9for (int i = 1; i < 19; ++i) {
10uint64_t result;
11│     pthread_join(threads[i - 1], (void **)&result);
12│     sum += result;}
13printf("∑ᵢ₌₁¹⁸ i·(i+1) = %ld\n", sum);return 0;}

25. Describe what the lines marked with <-- do.
Then, describe in a single short sentence functionality of the following program.

#include <fcntl.h>#include <pthread.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>
  │ 
  │ void *process(void *arg) {
 1int fd = open((char *)arg, O_RDONLY);
 2if (fd == -1) return NULL;unsigned char c;
 3unsigned *buf = malloc(256 * sizeof(unsigned));
 4memset(buf, 0, 256 * sizeof(unsigned));
 5while (read(fd, &c, 1) == 1);                         // <--
 6│     buf[c]++;                                           // <--
 7│   close(fd);
 8return buf;}
  │ 
  │ void outputAndFree(char *name, unsigned *buf) {
 9printf("%10s:", name);
10if (!buf) {
11puts(" failed");
12return;}
13for (unsigned char i = 'a'; i <= 'z'; ++i)
14printf(" %4u", buf[i]);
15putchar('\n');
16free(buf);}
  │ 
  │ int main(int argc, char *argv[]) {
17if (argc < 2) return 1;
18│   pthread_t *tid = malloc(argc * sizeof(pthread_t));
19for (int i = 2; i < argc; ++i)
20│     pthread_create(tid + i, NULL, process, argv[i]);    // <--
21unsigned *buf = process(argv[1]);
22│   outputAndFree(argv[1], buf);
23for (int i = 2; i < argc; ++i) {
24│     pthread_join(tid[i], (void **)&buf);                // <--
25│     outputAndFree(argv[i], buf);}return 0;}

26. Provide a shell command that produces the same output as the following program when invoked as program filename, where filename is a path to a text file.
Then, describe how the program operates to produce the output.

#include <fcntl.h>#include <unistd.h>#define MIN(a, b) (a < b ? a : b)int main(int argc, char *argv[]) {
 1if (argc != 2) return 1;int fd, cnt, pos;
 2if (( fd = open(argv[1], O_RDONLY)) == -1) return 1;
 3if ((pos = lseek(fd, -1, SEEK_END)) == -1) return 0;char buf[1024];
 4while (pos > 0) {
 5int i, bs = MIN(pos, 1024);
 6│     lseek(fd, pos - bs, SEEK_SET);
 7if (read(fd, buf, bs) != bs)
 8return 1;
 9for (i = bs; i > 0 && buf[--i] != '\n'; /**/);
10│     pos -= bs - i;
11if (buf[i] == '\n') {
12│       pos++;
13break;}}
14│   lseek(fd, pos, SEEK_SET);
15while ((cnt = read(fd, buf, 1024)) > 0)
16│     write(1, buf, cnt);
17│   close(fd);return 0;}

27. The code below might work by chance, but it contains a fragment that makes the behaviour of the code undefined. Point out which line is it, and describe a scenario when the problem can manifest.

#include <pthread.h>#include <signal.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>1│ pthread_mutex_t stdOutMtx = PTHREAD_MUTEX_INITIALIZER;2void ctrlc(int _) {
 3│   pthread_mutex_lock(&stdOutMtx);
 4printf("Quitting!\n");
 5│   pthread_mutex_unlock(&stdOutMtx);
 6exit(0);}
  │ 
  │ int mainPipe[2];int trianglePipe[2];int squarePipe[2];
  │ 
  │ void *triangle(void *_) {
 7│   pthread_detach(pthread_self());
 8while (1) {int n;
 9│     read(trianglePipe[0], &n, 4);
10│     pthread_mutex_lock(&stdOutMtx);
11for (int i = 0; i < n; i++) {
12for (int j = i; j < n; j++) {
13│         usleep(10000);
14putchar('*'); fflush(stdout);}
15putchar('\n');}
16│     pthread_mutex_unlock(&stdOutMtx);
17│     write(mainPipe[1], &n, 1);}}
  │ 
  │ // square is identical to triangle with an exception in line marked by '<--'void *square(void *_) { 
  │   pthread_detach(pthread_self());while (1) {int n;
  │     read(squarePipe[0], &n, 4);
  │     pthread_mutex_lock(&stdOutMtx);for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {      // <--
  │         usleep(10000);putchar('*'); fflush(stdout);}putchar('\n');}
  │     pthread_mutex_unlock(&stdOutMtx);
  │     write(mainPipe[1], &n, 1);}}
  │
  │ int main() {int32_t n;char t;
  │   pthread_t tid;
18│   signal(SIGINT, ctrlc);
19│   pipe(mainPipe); pipe(trianglePipe); pipe(squarePipe);
20│   pthread_create(&tid, NULL, triangle, NULL);
21│   pthread_create(&tid, NULL, square, NULL);
22while (1) {
23printf("Provide a dimension: ");  fflush(stdout);
24scanf("%d", &n);
25printf("Provide a type [t/s]: "); fflush(stdout);
26scanf("%*[\n]%c", &t);
27switch (t) {case 't':
28│         write(trianglePipe[1], &n, 4);
29│         read(mainPipe[0], &t, 1);break;case 's':
30│         write(squarePipe[1], &n, 4);
31│         read(mainPipe[0], &t, 1);break;}}return 0;}

28. Describe what this code does, and think up and provide one use case for it.

#include <unistd.h>#include <stdlib.h>#include <semaphore.h>#include <sys/wait.h>#include <fcntl.h>int main(int argc, char *argv[]){
 1│     sem_t * sem = sem_open (argv[1], O_RDWR|O_CREAT, 0600, atoi(argv[2]));
 2│     sem_wait(sem);
 3if(!fork()){
 4│         execvp(argv[3], argv+3);
 5return -1;}
 6│     close(0); close(1); close(2);
 7int rv;
 8│     wait(&rv);
 9│     sem_post(sem);
10return rv;}

29. The code below is a simple implementation of a linked-list based queue with a blocking get. Fill the gap with code that initializes the semaphores.

#include <semaphore.h>
#include <stdlib.h>
 
typedef int value_type;
struct llqueue {
  struct element {
    value_type value;
    struct element *next;
  } *head, *tail;
  sem_t count;
  sem_t excl;
};
 
void llqueue_put(struct llqueue *ll, value_type v) {
  struct element *e = malloc(sizeof(struct element));
  e->value = v;
  e->next = NULL;
  sem_wait(&ll->excl);
  if (ll->tail == NULL)
    ll->head = e;
  ll->tail = e;
  sem_post(&ll->excl);
  sem_post(&ll->count);
}
 
value_type llqueue_get(struct llqueue *ll) {
  sem_wait(&ll->count);
  sem_wait(&ll->excl);
  struct element *h = ll->head;
  value_type v = h->value;
  if (h->next == NULL)
    ll->tail = NULL;
  ll->head = h->next;
  sem_post(&ll->excl);
  free(h);
  return v;
}
 
void llqueue_init(struct llqueue *ll) {
  ll->head = ll->tail = NULL;
  ┌───────────────────────┐
  │                       │
  └───────────────────────┘
}