1 of 49

Multithreading

.NET

kontur.ru

https://github.com/kontur-courses/multithread-async

Клабуков Эрик

Старший инженер-программист

Лиясов Сергей

инженер-программист

2 of 49

Тест по материалу подготовки

Пройти тест можно перейдя по ссылке: https://forms.gle/fWuMLseguYdtJJHW8

Или отсканировав QR

3 of 49

Зачем все это нужно�

4 of 49

Зачем это все нужно (v2)

Рендеринг интерфейса графического приложения не должен зависать:

  • I/O должен быть вынесен в отдельные потоки (или использовать eventloop без ожиданий)
  • Все операции должны быть распараллелины, чтобы как можно быстрее выполнить обработку и показать результат пользователю
  • Тяжелые расчеты и операции также не должны выполняться в основном потоке интерфейса

5 of 49

Процесс

  • Экземпляр исполняемого файла
  • Обладает:
    • Виртуальной памятью (код, данные)
    • Handles (сокеты, файловые дескрыпторы, ...)
    • Security Tokens
    • Переменные окружения
  • Имеет 1+ поток выполнения
  • Priority class
  • Affinity

6 of 49

Представление памяти процесса

7 of 49

Поток выполнения (Thread)

  • Самостоятельная последовательность инструкций
  • Ресурсы процесса общие между всеми потоками
  • Объект, управляемый планировщиком ОС
    • Свой контекст (стек, регистры)
  • Уровень приоритета
    • => Base = F(Priority Class, Priority Level)
  • Разделяет процессорное время со�всеми остальными потоками ОС
  • Affinity

8 of 49

Многозадачность

  • Кооперативная
    • Каждый поток сам заботится о передачи�процессорного времени
    • За рамками рассмотрения:
      • UMS (User-mode scheduling) threads
      • Fibres
  • Вытесняющая
    • Планировщик ОС принудительно �переключает ядра процессора между потоками учитывая приоритеты

9 of 49

Вытесняющая многозадачность

10 of 49

Приоритеты потоков

  • Base Priority = F(Priority Class, Priority Level)
  • 0 - idle
  • 1..15 - dynamic
  • 16..31 - realtime

11 of 49

Планировщик потоков

  • Quantum (time slice) ~30ms или ~200ms
  • Переключение контекста возможно:
    • Закончился квант
    • Поток ожидает события (блокирока/io/..)
    • Появился более высокоприоритетный �поток

12 of 49

Динамический приоритет планировщика

  • Увеличение приоритета
    • Событие клавиатуры/мыши
    • Выход из состояния Wait
    • Окно стало активным
    • Поток ~3-4 сек. не получал квант времени
      • Увеличение приоритета сразу до 15
      • Увеличение длины кванта
  • Не применяется для RealTime
  • Выдается временно: очередной квант => -1 к приоритету и так до Base приоритета

13 of 49

Многоядерность

14 of 49

Закон Амдала

Закон Амдала гласит, что максимальное ускорение, которое можно получить от параллелизации задачи, ограничено долей задачи, которая не может быть распараллелена.

Например, если 20% задачи нельзя распараллелить (f = 0,2), то максимальное ускорение, которое можно получить с использованием бесконечного количества процессоров (n стремится к бесконечности), составляет:

S = 1 / (1 - 0,2 + 0,2/∞) = 1 / 0,2 = 5

15 of 49

Эксперимент (30 минут)

Напишите программу, которая вычислит размер кванта времени планировщика потоков.�Воспользуйтесь заготовкой samples/QuantumOfSwitching

�Вероятно, вам понадобится больше одного потока в своей программе.�Для более достоверного расчета, вам нужно будет искусственно привязать все потоки своего процесса к одному ядру

    • Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)(1 << processorNum)

16 of 49

Примитивы синхронизации

17 of 49

Monitor и lock

var lockObj = new Object();

bool lockTaken = false;

try

{

Monitor.Enter(lockObj, ref lockTaken);

// Our code

}

finally

{

// Ensure that the lock is released.

if(lockTaken)

Monitor.Exit(lockObj);

}

var lockObj = new Object();

lock (lockObj)

{

// Our code

}

18 of 49

Thread Pool

19 of 49

Эксперимент (30 минут)

Напишите собственную реализацию пула потоков.�Воспользуйтесь заготовкой samples/ThreadPool

�Обойдитесь без использования

    • SpinWait.SpinUntil
    • BlockingCollection (BlockingQueue, ConcurrentStack,
    • ConcurrentQueue, ConcurrentBag)

20 of 49

Monitor.Pulse и Monitor.Wait

21 of 49

Thread Pool .NET 4.0 (и выше)

22 of 49

Динамический размер пула потоков

Тред пул в .NET является динамическим и может управляться через

  • .SetMinThreads() - для выставления минимального количества потоков в пуле
  • .SetMaxThreads() - для выставления максимального количества потоков в пуле

23 of 49

LockFree

  • Атомарные операции ( Interlocked )
    • System.Collections.Concurrent
      • ConcurrentStack
        • Interlocked.CompareExchange
      • ConcurrentQueue
        • тоже Interlocked.CompareExchange
      • ConcurrentDictionary
        • комбинация Interlocked + lock
  • Барьеры памяти и Модель памяти .NET
    • volatile
    • Volatile.Read и Volatile.Write
    • Thread.MemoryBarrier()

24 of 49

Interlocked

Метод

Описание

Сигнатура

Increment

Атомарно увеличивает значение на 1

int Increment(ref int count)

Decrement

Атомарно уменьшает значение на 1

int Decrement(ref int count)

Exchange

Атомарно заменяет значение и возвращает старое

object Exchange(ref object location, object value)

CompareExchange

Заменяет значение только если текущее равно expected

object CompareExchange(ref object location, object value, object comparand)

25 of 49

Эксперимент (30 минут)

Реализуйте lock free версии стека и очереди

Воспользуйтесь заготовкой samples/LockFree

При реализации нельзя использовать блокировки (т.к. это lock free)

А также срезать путь делая обертку над ConcurrentQueue и ConcurrentStack

26 of 49

Data Parallelism

  • Parallel LINQ (PLINQ)
  • Parallel class
    • Parallel.Invoke
    • Parallel.ForEach
    • Parallel.For

Примечание: PLINQ и Parallel являются частью TPL

Но более подробнее TPL разберем далее

27 of 49

PLINQ

28 of 49

Concurrency

  • .WithExecutionMode()
    • ParallelExecutionMode.ForceParallelism
  • .WithDegreeOfParallelism()
    • 1..N=512

29 of 49

Order

  • .AsOrdered()
    • Order
    • Deterministic output for some operators
    • Performance degrading
  • .AsUnordered()

30 of 49

Buffering

  • .WithMergeOptions()
    • AutoBuffered
    • NotBuffered
    • FullyBuffered

  • .WithCancellation()

31 of 49

Buffering

MergeOptions

Порядок элементов

Производительность

Задержка

NotBuffered

Не сохраняется

Высокая

Минимальная

AutoBuffered

Может сохраняться

Оптимальная

Средняя

FullyBuffered

Может сохраняться

Ниже

Максимальная

Default

Как AutoBuffered

Оптимальная

Средняя

32 of 49

Partitioning

33 of 49

Как добится параллелизма

Число ядер CPU > 1

Отсутствие блокировок (распараллеливаемость)

Делегаты не слишком маленькие (вычислительная сложность не превышает накладные расходы)

Сохранение порядка может убить производительность (порядок не важен)

34 of 49

TPL (Task Parallel Library)

Набор готовых классов и методов для более простого распараллеливания кода приложения

Поддержка асинхронного программирования которое не блокирует исполнение приложения (потоки возвращаются в пул и выполняют полезную работу)

Дает инструменты по управлению отменой, управлению состоянием и получению результатов

Появился в .NET Framework 4.0 (2010 год)

35 of 49

Класс Task

  • AsyncState: возвращает объект состояния задачи
  • CurrentId: возвращает идентификатор текущей задачи (статическое свойство)
  • Id: возвращает идентификатор текущей задачи
  • Exception: возвращает объект исключения, возникшего при выполнении задачи
  • Status: возвращает статус задачи. Представляет перечисление System.Threading.Tasks.TaskStatus, которое имеет следующие значения:
    • Canceled: задача отменена
    • Created: задача создана, но еще не запущена
    • Faulted: в процессе работы задачи произошло исключение
    • RanToCompletion: задача успешно завершена
    • Running: задача запущена, но еще не завершена
    • WaitingForActivation: задача ожидает активации и постановки в график выполнения
    • WaitingForChildrenToComplete: задача завершена и теперь ожидает завершения прикрепленных к ней дочерних задач
    • WaitingToRun: задача поставлена в график выполнения, но еще не начала свое выполнение
  • IsCompleted: возвращает true, если задача завершена
  • IsCanceled: возвращает true, если задача была отменена
  • IsFaulted: возвращает true, если задача завершилась при возникновении исключения
  • IsCompletedSuccessfully: возвращает true, если задача завершилась успешно
  • Result: если задача была с возвращаемым значением Task<T> (T- тип результата), то в поле будет результат выполнения

36 of 49

Что еще дает Task?

Task не только содержит информацию о задаче

которую мы отправили на выполнение в пул потоков, но и предоставляет дополнительные средства для:

  • Ожидания выполнения
  • Готовых синхронных ответов
  • Простой интерфейс постановки наших задач на выполнение в ThreadPool

37 of 49

38 of 49

39 of 49

Получение результата и исключения до TPL

  1. Нужно было делать отдельное событие для ожидания результата
  2. Нужно подготовить переменные для хранения результата и исключения
  3. Происходит замыкание и захват переменных
  4. Исключения обрабатываются внутри делегата

40 of 49

Как это стало после появления TPL

  • Никаких замыканий при выполнении
  • Task сам в себе содержит исключения, результат и статус
  • Обработка исключений происходит там, где мы ожидаем и получаем результат

41 of 49

Async/Await

  • Механизм асинхронного программирования, который позволяет писать код, который не блокирует исполнение приложения
  • Async - помечает метод/делегат как асинхронный
  • Await - ключевое слово которое может применяться только в асинхронных методах и над асинхронными методами. Приостанавливает выполнение метода пока другой асинхронный метод не завершится. При этом поток высвобождается для выполнения полезной работы

42 of 49

43 of 49

44 of 49

Эксперимент (30 минут)

Есть простая реализация NMAP где используется синхронный последовательный обход набора ip адресов с проверкой их доступности.�Необходимо реализовать `AsyncScanner` сделав проверку параллельной и асинхронной

Воспользуйтесь заготовкой samples/NMAP

Все методы должны быть асинхронными (Значит возвращать Task<T>)

Для распараллеливания использовать Parallel.ForEachAsync

Использовать асинхронные перегрузки у методов за место� синхронных (Оканчиваются на Async или возвращают Task)

45 of 49

Эксперимент (совместный)

Посмотрим на реализацию ClusterServer, режим синхронной и асинхронной обработки поступающих запросов

Запустим нагрузочное тестирование, чтобы посмотреть на скорость обработки запросов и пропускную способность синхронного и асинхронного режимов

Посмотрим в реальном времени, как нагружается ThreadPool в синхронном

и асинхронном режимах

46 of 49

Эксперимент (1 час 20 минут)

Задача: Написать децентрализованный чат, который будет сам искать клиентов в сети ( на указанных ip адресах), подключаться друг к другу (каждый к каждому) и пересылать сообщения между клиентами

Воспользуйтесь заготовкой samples/HackChat

Список ip адресов может быть захардкожен (не обязательно принимать

в аргументах)

На Windows/Linux можно локально запускать экземпляры на адресах

127.0.0.1 , 127.0.0.2, 127.0.0.3 для имитации нескольких клиентов

На MacOS такое не сработает, там можно разойтись только по

портам и использовать можно только 127.0.0.1

Не забудьте проверять, что клиент к кому вы подключаетесь уже не подключился к вам сам), как и защиту от подключения к самому себе

47 of 49

Домашнее задание

Необходимо реализовать 3 стратегии обхода реплик для ClusterClient:

  • Parallel (1 балл). Ходить ко всем репликам одновременно и дождаться первого ответа от одного из них
  • RoundRobin (2 балла). Делить таймаут на количество реплик и последовательно делать запросы к ним. Если реплика не ответила за таймаут, переходить к следующей
  • Smart (2 балла). Улучшенный RoundRobin, где после перехода к следующей реплике мы все еще ждем и проверяем ответы от предыдущих

Подробное описание домашнего задания в репозитории

в homework 2/README.md

В проекте ClusterTests находятся соответствующие клиентам тесты, реализации клиентов должны стабильно проходить все соответствующие тесты.

48 of 49

Благодарю

за внимание!

Вопросы?

Эрик Клабуков

Старший инженер-программист

kontur.ru

49 of 49

Дополнительный материал