os_cp:threads:solutions
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revision | |||
| os_cp:threads:solutions [2023/09/15 13:21] – jkonczak | os_cp:threads:solutions [2024/05/07 20:40] (current) – jkonczak | ||
|---|---|---|---|
| Line 98: | Line 98: | ||
| } | } | ||
| </ | </ | ||
| + | |||
| + | ~~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 < | #include < | ||
| #include < | #include < | ||
| + | #include < | ||
| 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 = ' | for (char letter = ' | ||
| pthread_mutex_lock(& | pthread_mutex_lock(& | ||
| - | for (int i = 0; i < 999; ++i) | + | |
| - | | + | |
| + | | ||
| pthread_mutex_unlock(& | pthread_mutex_unlock(& | ||
| } | } | ||
| Line 120: | Line 141: | ||
| int main() { | int main() { | ||
| - | pthread_t | + | pthread_t |
| - | pthread_create(& | + | pthread_create(& |
| - | pthread_detach(thread); | + | |
| while (getchar() != EOF) { | while (getchar() != EOF) { | ||
| pthread_mutex_lock(& | pthread_mutex_lock(& | ||
| - | printf(" | + | |
| + | // 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, text, 1020); | ||
| pthread_mutex_unlock(& | pthread_mutex_unlock(& | ||
| + | printf(" | ||
| } | } | ||
| + | return 0; | ||
| + | } | ||
| + | </ | ||
| + | <code c> | ||
| + | #include < | ||
| + | #include < | ||
| + | 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(& | ||
| + | counter++; | ||
| + | pthread_mutex_unlock(& | ||
| + | } | ||
| + | return NULL; | ||
| + | } | ||
| + | |||
| + | int main() { | ||
| + | pthread_t tid[16]; | ||
| + | for (int i = 0; i < 16; ++i) | ||
| + | pthread_create(tid + i, NULL, incrementer, | ||
| + | for (int i = 0; i < 16; ++i) | ||
| + | pthread_join(tid[i], | ||
| + | printf(" | ||
| + | return 0; | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | < | ||
| + | Notice that the second example can also be amended more efficiently by using atomic operations: | ||
| + | <code c> | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | |||
| + | _Atomic unsigned long long counter; | ||
| + | |||
| + | void *incrementer(void *arg) { | ||
| + | for (int i = 0; i < 1000; ++i) | ||
| + | atomic_fetch_add(& | ||
| + | return NULL; | ||
| + | } | ||
| + | |||
| + | int main() { | ||
| + | pthread_t tid[16]; | ||
| + | for (int i = 0; i < 16; ++i) | ||
| + | pthread_create(tid + i, NULL, incrementer, | ||
| + | for (int i = 0; i < 16; ++i) | ||
| + | pthread_join(tid[i], | ||
| + | printf(" | ||
| return 0; | return 0; | ||
| } | } | ||
| </ | </ | ||
| + | </ | ||
| ~~Exercise.# | ~~Exercise.# | ||
| Line 242: | Line 319: | ||
| return 0; | return 0; | ||
| } | } | ||
| + | </ | ||
| + | |||
| + | ~~Exercise.# | ||
| + | |||
| + | Critical sections in this particular example are the lines between '' | ||
| + | |||
| + | ~~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(& | ||
| + | pthread_mutex_lock(& | ||
| </ | </ | ||
| Line 324: | Line 417: | ||
| ... | ... | ||
| </ | </ | ||
| + | |||
| + | ~~Exercise.# | ||
| + | |||
| + | <code c># | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | |||
| + | #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(& | ||
| + | while (head == NULL) | ||
| + | pthread_cond_wait(& | ||
| + | struct list *e = head; | ||
| + | head = e->next; | ||
| + | if (head != NULL) | ||
| + | pthread_cond_signal(& | ||
| + | pthread_mutex_unlock(& | ||
| + | |||
| + | printf(" | ||
| + | EXECUTE_TASK(e-> | ||
| + | printf(" | ||
| + | free(e); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | int main() { | ||
| + | for (intptr_t i = 0; i < N; ++i) { | ||
| + | pthread_t tid; | ||
| + | pthread_create(& | ||
| + | pthread_detach(tid); | ||
| + | } | ||
| + | |||
| + | int td; | ||
| + | while (scanf(" | ||
| + | printf(" | ||
| + | struct list *e = malloc(sizeof(struct list)); | ||
| + | *e = (struct list){.task = td, .next = NULL}; | ||
| + | |||
| + | pthread_mutex_lock(& | ||
| + | if (head == NULL) { | ||
| + | head = e; | ||
| + | pthread_cond_signal(& | ||
| + | } else { | ||
| + | struct list *it = head; | ||
| + | while (it-> | ||
| + | it = it-> | ||
| + | it->next = e; | ||
| + | } | ||
| + | pthread_mutex_unlock(& | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| < | < | ||
os_cp/threads/solutions.1694784117.txt.gz · Last modified: (external edit)