суббота, 29 февраля 2020 г.

Job system

Понемногу пишу движок, на котром обкатываю новые фичи. Среди них - планировщик задач (thread pool, job system, ...).
Какой требуется функционал:
  • Зависимости между тасками - то есть таск запускается только когда все его зависимости завершились.
  • Отмена тасков - до захвата таска потоком отмену обрабатывает сам планировщик, после - поток в котором выполняется таск.
  • Слабые и сильные зависимости - определяет что будет при отмене зависимого таска. При слабой зависимости следующий таск запустится даже при отмене зависимого таска. При сильной зависимости следующий таск также отменяется.
  • Кастомные зависимости - таким образом можно добавить зависимость между таском и GPU через VkFence, например.
  • Совместимость с другими системами - работа с сетью также сделана на тасках, многопоточный ECS и рендер тоже будет на них и тд.
  • Поддержка блокировок - для этого сделана отдельная зависимость InterlockDependency, куда передается callback, который может захватить мютекс или любой другой примитив синхронизации. Особенность в том, что захват идет через try_lock и если захватить не получилось, то таск остается в очереди до следующей проверки, если же получилось захватить, то таск отправляется на выполнение, а после выполнения вызывает функцию что бы разблокировать. Это позволяет не блокировать потоки при доступе к общим данным.
Исходники можно посмотреть тут.

четверг, 13 февраля 2020 г.

Виртуальная машина для тестирования lock-free

Я знаю, что в clang есть санитайзер для проверки lock-free (и подобных на атомиках) алгоритмов, но мне нужно было что-то более универсальное, поэтому написал свою виртуальную машину, которая подменяет атомики, мютексы и прочее на свою реализацию.

Идея в том, что есть несколько независимых функций, использующих lock-free алгоритм, они рандомно запускаются на множестве потоков, а все синхронизации и кэш флуши, все чтение и запись в общую память сделано через виртуальную машину, где и происходят проверки. Потоков сделано чуть больше чем доступно хардварных потоков, чтоб ОС постоянно приостанавливала работу одних и запускала другие. После обращения к атомикам поток усыпляется на случайное время, это позволяет ловить ошибки, когда операции с двумя и более атомиками никак не синхронизированны и кто-то может вклиниться между ними.

Что обнаруживает виртуальная машина:
  • Data race - параллельное чтение и запись в одну и ту же память.
  • Пропущенные flush / invalidate cache, когда в другом потоке произошел flush, а в текущем читают без вызова invalidate. Либо когда в память произошла запись, но не вызвался flush.
Ошибки в работе с кэшем невозможно обнаружить на x86 и x64 архитектурах из-за особенностей их работы с кэшем (CISC инструкции). Но эти проблемы проявляются на ARM (RISC инструкции), а постоянно тестировать на телефоне не так уж удобно, тем более проблемы могут очень долго не проявляться.

Исходники и примеры тут.