os_labs/lab11/README.md

145 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Лабораторная работа №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 работает медленнее других в любом случае.