User Tools

Site Tools


os_cp:threads:solutions

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
os_cp:threads:solutions [2023/09/15 15:21]
jkonczak
os_cp:threads:solutions [2024/05/07 22:40] (current)
jkonczak
Line 98: Line 98:
 } }
 </​code>​ </​code>​
 +
 +~~Exercise.#​~~
 +
 +The examples show that accessing data with no synchronisation may lead to
 +inconsistent results.
 +\\
 +In the first code example one thread updates a text (character array) to a new
 +version, whereas the second thread attempts to read the text. However, the 
 +thread reading the text sees some characters in the text being part of one
 +version and some other characters that being from another version.
 +\\
 +While the first code example shows that distinct data items can be out of sync,
 +in the second code example the same 64-bit wide integer is used by multiple
 +threads. Each of the 16 threads increments the integer one thousand times, but
 +the result happens to be less than 16 thousands. This shows that even accesses
 +to a single main memory location must be handled with care in concurrent
 +programs.
 +\\
 +In general, the examples motivate need for mutexes.
  
 ~~Exercise.#​~~ ~~Exercise.#​~~
Line 104: Line 123:
 #include <​pthread.h>​ #include <​pthread.h>​
 #include <​stdio.h>​ #include <​stdio.h>​
 +#include <​string.h>​
  
 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;​ pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;​
 +unsigned long long version;
 +char text[1020];
  
-char buffer[1000];​ +void *updater(void *arg) {
- +
-void *writer(void *arg) {+
   while (1)   while (1)
     for (char letter = '​a';​ letter <= '​z';​ ++letter) {     for (char letter = '​a';​ letter <= '​z';​ ++letter) {
       pthread_mutex_lock(&​mutex);​       pthread_mutex_lock(&​mutex);​
-      for (int i = 0; i < 999; ++i) +      ​version++;​ 
-        ​buffer[i] = letter;+      ​for (int i = 0; i < 1020 - 1; ++i) 
 +        ​text[i] = letter;
       pthread_mutex_unlock(&​mutex);​       pthread_mutex_unlock(&​mutex);​
     }     }
Line 120: Line 141:
  
 int main() { int main() {
-  pthread_t ​thread+  pthread_t ​tid
-  pthread_create(&​thread, NULL, writer, NULL); +  pthread_create(&​tid, NULL, updater, NULL);
-  pthread_detach(thread);​ +
   while (getchar() != EOF) {   while (getchar() != EOF) {
     pthread_mutex_lock(&​mutex);​     pthread_mutex_lock(&​mutex);​
-    printf("​%s\n"​buffer);+    ​// the data is copied locally to make the critical section short 
 +    // and to avoid an I/O operation (printf) in the critical section 
 +    unsigned long long version_copy;​ 
 +    char text_copy[1020];​ 
 +    version_copy = version; 
 +    memcpy(text_copy, text1020);
     pthread_mutex_unlock(&​mutex);​     pthread_mutex_unlock(&​mutex);​
 +    printf("​version:​ %llu\n ​  text: %s\n", version_copy,​ text_copy);
   }   }
 +  return 0;
 +}
 +</​code>​
 +<code c>
 +#include <​pthread.h>​
 +#include <​stdio.h>​
  
 +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;​
 +unsigned long long counter;
 +
 +void *incrementer(void *arg) {
 +  for (int i = 0; i < 1000; ++i) {
 +    pthread_mutex_lock(&​mutex);​
 +    counter++;
 +    pthread_mutex_unlock(&​mutex);​
 +  }
 +  return NULL;
 +}
 +
 +int main() {
 +  pthread_t tid[16];
 +  for (int i = 0; i < 16; ++i)
 +    pthread_create(tid + i, NULL, incrementer,​ NULL);
 +  for (int i = 0; i < 16; ++i)
 +    pthread_join(tid[i],​ NULL);
 +  printf("​%llu\n",​ counter);
 +  return 0;
 +}
 +</​code>​
 +
 +<​small>​
 +Notice that the second example can also be amended more efficiently by using atomic operations:
 +<code c>
 +#include <​pthread.h>​
 +#include <​stdatomic.h>​
 +#include <​stdio.h>​
 +
 +_Atomic unsigned long long counter;
 +
 +void *incrementer(void *arg) {
 +  for (int i = 0; i < 1000; ++i)
 +    atomic_fetch_add(&​counter,​ 1);
 +  return NULL;
 +}
 +
 +int main() {
 +  pthread_t tid[16];
 +  for (int i = 0; i < 16; ++i)
 +    pthread_create(tid + i, NULL, incrementer,​ NULL);
 +  for (int i = 0; i < 16; ++i)
 +    pthread_join(tid[i],​ NULL);
 +  printf("​%llu\n",​ counter);
   return 0;   return 0;
 } }
 </​code>​ </​code>​
 +</​small>​
  
 ~~Exercise.#​~~ ~~Exercise.#​~~
Line 242: Line 319:
   return 0;   return 0;
 } }
 +</​code>​
 +
 +~~Exercise.#​~~
 +
 +Critical sections in this particular example are the lines between ''​pthread_mutex_lock''​(s) and ''​pthread_mutex_unlock''​(s) ​
 +
 +~~Exercise.#​~~
 +
 +For instance, an attempt to exchange item 0 with item 1 in thread #2 concurrently with an attempt to exchange item 1 with item 0 in thread #3 can lead to a situation when thread #2 successfully locks mutex on item 0 and thread #3 successfully locks mutex on item 1. Then, neither thread can proceed, for the other of the two threads holds the required mutex.
 +
 +~~Exercise.#​~~
 +
 +A common solution to prevent deadlocks is to always lock mutexes in predefined order. For instance, the lines locking the mutexes upon exchanging items could look like this:
 +<code c>
 +      pthread_mutex_lock(&​item[argOne < argTwo ? argOne : argTwo].mtx);​
 +      pthread_mutex_lock(&​item[argOne < argTwo ? argTwo : argOne].mtx);​
 </​code>​ </​code>​
  
Line 324: Line 417:
 ... ...
 </​code>​ </​code>​
 +
 +~~Exercise.#​~~
 +
 +<code c>#​include <​pthread.h>​
 +#include <​stdint.h>​
 +#include <​stdio.h>​
 +#include <​stdlib.h>​
 +#include <​unistd.h>​
 +
 +#define N 4
 +
 +typedef int task_specification_t;​
 +#define EXECUTE_TASK(arg) sleep(arg)
 +
 +struct list {
 +  task_specification_t task;
 +  struct list *next;
 +} *head;
 +pthread_mutex_t list_mutex = PTHREAD_MUTEX_INITIALIZER;​
 +pthread_cond_t list_cv = PTHREAD_COND_INITIALIZER;​
 +
 +void *worker(void *arg) {
 +  while (1) {
 +    pthread_mutex_lock(&​list_mutex);​
 +    while (head == NULL)
 +      pthread_cond_wait(&​list_cv,​ &​list_mutex);​
 +    struct list *e = head;
 +    head = e->next;
 +    if (head != NULL)
 +      pthread_cond_signal(&​list_cv);​
 +    pthread_mutex_unlock(&​list_mutex);​
 +
 +    printf("​Worker %ld executing task %d\n", (intptr_t)arg,​ e->​task);​
 +    EXECUTE_TASK(e->​task);​
 +    printf("​Worker %ld finished ​ task %d\n", (intptr_t)arg,​ e->​task);​
 +    free(e);
 +  }
 +}
 +
 +int main() {
 +  for (intptr_t i = 0; i < N; ++i) {
 +    pthread_t tid;
 +    pthread_create(&​tid,​ NULL, worker, (void *)i);
 +    pthread_detach(tid);​
 +  }
 +
 +  int td;
 +  while (scanf("​%d",​ &td) != EOF) {
 +    printf("​Dispatching task %d\n", td);
 +    struct list *e = malloc(sizeof(struct list));
 +    *e = (struct list){.task = td, .next = NULL};
 +
 +    pthread_mutex_lock(&​list_mutex);​
 +    if (head == NULL) {
 +      head = e;
 +      pthread_cond_signal(&​list_cv);​
 +    } else {
 +      struct list *it = head;
 +      while (it->​next != NULL)
 +        it = it->​next;​
 +      it->next = e;
 +    }
 +    pthread_mutex_unlock(&​list_mutex);​
 +  }
 +}
 +</​code>​
 +
 +
  
 <​html></​div></​html>​ <​html></​div></​html>​
os_cp/threads/solutions.txt · Last modified: 2024/05/07 22:40 by jkonczak