# Лабораторная работа №11 ## Задание 1 > vector-deadlock.c, main-common.c и др.: > - Выполнить ./vector-deadlock -n 2 -l 1 -v > которая инициирует 2 потока (-n 2) и каждый из них осуществляет одно сложение (-l 1) с опцией (-v). > Объяснить результат. Меняется ли он от вызова к вызову? > - Добавить флаг -d и изменить количество циклов -l . > Всегда ли возникает состояние взаимной блокировки потоков (deadlock)? > - Теперь меняем число потоков -n. Есть ли такое число потоков, при котором блокировка не возникает? Программа печатает в каждом потоке начальные данные перед `vector_add` и результат выполнения данной функции. Результат может меняться от вызова к вызову, так как весь worker не покрыт мьютексами, но на практике на такой небольшой программе это маловероятно достижимо. Обычно программа возвращает следующее: ```text ->add(0, 1) <-add(0, 1) ->add(0, 1) <-add(0, 1) ``` Спустя примерно 30 запусков, удалось получить другой результат: ```text ->add(0, 1) ->add(0, 1) <-add(0, 1) <-add(0, 1) ``` При добавлении `-d` взаимная блокировка возникает не всегда, а только при попадании переключения потоков между мьютексами. Удалось достичь стабильный дедлок при запуске с ключами `-t -n 2 -l 100 -d` (без `-v`) В случае, если число потоков = 1, взаимной блокировки не возникнет. ## Задание 2 > vector-global-order.c: > - За счет чего программа избегает блокировок? > - Для чего предусмотрен специальный случай в vector add(), когда исходный и результирующий вектор совпадают? > - Флаги: -t -n 2 -l 100000 -d. Как меняется время в зависимости от числа циклов и числа потоков? > - Что происходит, когда включается ключ -p (при сложении различных векторов и одного и того же)? Программа избегает мёртвой блокировки за счёт упорядочивания по адресам, что позволяет постоянно сохранять порядок блокировки. В случае если адреса совпадают, то это один мьютекс, и для корректной работы программы его надо блокировать 1 раз. В случае увеличения числа циклов и потоков, время выполнения растёт. В случае включения `-p` время уменьшается, так как разрешается параллелизм. ## Задание 3 > vector-try-wait.c: > - Нужен ли первый вызов pthread_mutex_trylock()? > - Как меняется число повторных попыток, когда растет число потоков? Вызовы pthread_mutex_trylock необходимы для создания порядка блокировки, для того чтобы избежать дедлока. ```text $ ./vector-try-wait -t -n 2 -l 100 -d Retries: 0 Time: 0.00 seconds $ ./vector-try-wait -t -n 4 -l 100 -d Retries: 847 Time: 0.00 seconds ``` С увеличением числа потоков происходит рост повторных попыток, что является логичным, так как переключение между потоками становится более частым. При использовании `-p` повторных попыток не возникает. ## Задание 4 > vector-avoid-hold-and-wait.c: > - Сравнить с другими подходами. > - Как меняется производительность в зависимости от наличия флага -p? Данный подход защищает уязвимое место дедлока созданием глобального мьютекса, но при этом не даёт различным векторам выполняться параллельно. При использовании `-p` время уменьшается. ## Задание 5 > vector-nolock.c: > - Сравнить семантику и производительность с другими вариантами при работе с двумя одинаковыми векторами и в случае, > когда каждый поток работает на своем векторе -p. Указав memory, программа дожидается завершения всех операцией с памятью, что позволяет заменить мьютексы в данном случае. (https://ru.wikipedia.org/wiki/GCC_Inline_Assembly) Фактически в программе производится атомарное сложение, и в стандарте C11 для этих целей есть особые типы: (https://en.cppreference.com/w/c/language/atomic) Также в C11 ввели поддержку потоков в стандартную библиотеку, что позволяет писать кроссплатформенный код: (https://en.cppreference.com/w/c/thread) Но атомарные операции очень дорого стоят. Сравним время выполнения следующих команд: ```text $ ./vector-nolock -t -n 2 -l 1000000 -d Time: 7.20 seconds $ ./vector-nolock -t -n 2 -l 1000000 -d -p Time: 1.07 seconds ``` ```text $ ./vector-avoid-hold-and-wait -t -n 2 -l 1000000 -d Time: 4.46 seconds $ ./vector-avoid-hold-and-wait -t -n 2 -l 1000000 -d -p Time: 0.40 seconds ``` ```text $ ./vector-try-wait -t -n 2 -l 1000000 -d Retries: 5979033 Time: 2.55 seconds $ ./vector-try-wait -t -n 2 -l 1000000 -d -p Retries: 0 Time: 0.25 seconds ``` ```text $ ./vector-global-order -t -n 2 -l 1000000 -d Time: 1.23 seconds $ ./vector-global-order -t -n 2 -l 1000000 -d -p Time: 0.26 seconds ``` Таким образом видно, что vector-nolock работает медленнее других в любом случае.