Документация Engee

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

Следующие стратегии гарантируют отсутствие взаимоблокировок в коде (как правило, путем соблюдения 4-го условия Коффмана: циклического ожидания).

  1. Код нужно структурировать таким образом, чтобы за один раз нужно было получать только одну блокировку.

  2. Общие блокировки всегда следует получать в том же порядке, как указано в таблице ниже.

  3. Необходимо избегать конструкций, в которых ожидается, что потребуется неограниченная рекурсия.

Блокировки

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

Следующие блокировки, безусловно, являются конечными блокировками (1-го уровня) и не должны пытаться получить какую-либо другую блокировку.

  • Точка безопасности

    Обратите внимание, что эту блокировку неявным образом получают JL_LOCK и JL_UNLOCK. Используйте варианты _NOGC, чтобы исключить такую ситуацию для блокировок 1-го уровня.

    Во время удержания этой блокировки код не должен выполнять никаких выделений или достигать точек безопасности. Обратите внимание, что существуют точки безопасности при выделении, включении или отключении сборки мусора, вводе или восстановлении фреймов исключений и принятии или освобождении блокировок.

  • shared_map

  • finalizers

  • pagealloc

  • gc_perm_lock

  • flisp

  • jl_in_stackwalk (Win32)

  • ResourcePool<?>::mutex

  • RLST_mutex

  • llvm_printing_mutex

  • jl_locked_stream::mutex

  • debuginfo_asyncsafe

  • inference_timing_mutex

  • ExecutionEngine::SessionLock

Сам flisp уже потокобезопасен. Эта блокировка защищает только пул jl_ast_context_list_t. Аналогичным образом, ResourcePool<?>::mutexes защищает только связанный пул ресурсов.

Далее приведена конечная блокировка (2-го уровня), которая внутренне получает только блокировки 1-го уровня (точка безопасности).

  • global_roots_lock

  • Module->lock

  • JLDebuginfoPlugin::PluginMutex

  • newly_inferred_mutex

Далее приведена блокировка 3-го уровня, которая может внутренне получать только блокировки 1-го или 2-го уровней.

  • Method->writelock

  • typecache

Далее приведена блокировка 4-го уровня, которая может рекурсивно получать только блокировки 1-го, 2-го или 3-го уровней.

  • MethodTable->writelock

Во время удержания блокировки выше этой точки код Julia вызывать невозможно.

Блокировки orc::ThreadSafeContext (TSCtx) занимают особое место в иерархии блокировки. Они служат для защиты глобального непотокобезопасного состояния LLVM, но их может быть произвольное количество. По умолчанию все эти блокировки могут считаться блокировками уровня 5 при сравнении с остальной иерархией. Получать TSCtx следует только из JIT-пула TSCtx, а все блокировки этого TSCtx должны быть сняты до его возврата в пул. Если одновременно нужно получить несколько блокировок TSCtx (из-за рекурсивной компиляции), то их следует получать в том же порядке, в котором блокировки TSCtx брались из пула.

Далее приведена блокировка 5-го уровня:

  • JuliaOJIT::EmissionMutex

Далее приведена блокировка 6-го уровня, которая может рекурсивно получать только блокировки более низких уровней.

  • jl_modules_mutex

Следующая блокировка синхронизирует работу ввода-вывода. Помните, что выполнение любой операции ввода-вывода (например, вывод предупреждающих сообщений или отладочной информации) при удержании любой другой блокировки, перечисленной выше, может привести к опасным и труднообнаруживаемым взаимоблокировкам. БУДЬТЕ ОЧЕНЬ ОСТОРОЖНЫ!

  • iolock

  • Отдельные блокировки ThreadSynchronizers

    Их можно продолжать удерживать после освобождения блокировки iolock или получать без нее, но будьте очень осторожны и не пытайтесь получить блокировку iolock, удерживая эти блокировки.

  • Блокировка Libdl.LazyLibrary

Далее приведена блокировка 7-го уровня, которая может быть получена только в том случае, если не удерживаются никакие другие блокировки.

  • world_counter_lock

Следующая блокировка является корневой, что означает, что при попытке ее получения нельзя удерживать другие блокировки.

  • toplevel

    Эту блокировку следует удерживать при попытке выполнения действия верхнего уровня (например, создание нового типа или определение нового метода): попытка получить эту блокировку внутри промежуточной функции приведет к возникновению условия взаимоблокировки.

    Кроме того, неясно, может ли любой код безопасно выполняться параллельно с произвольным выражением верхнего уровня. Поэтому может потребоваться, чтобы все потоки сначала достигали точки безопасности.

Неработающие блокировки

Следующие блокировки не работают.

  • toplevel

    Сейчас не существует > > исправление: создайте ее.

  • Module->lock

    Уязвима к взаимоблокировкам, поскольку нет уверенности, что она получена последовательно. > Некоторые операции (например, import_module) не имеют блокировки. > > Исправление: замените jl_modules_mutex?

  • loading.jl: require и register_root_module

    Этот файл потенциально имеет множество проблем. > > Исправление: требуются блокировки.

Общие глобальные структуры данных

Каждой такой структуре данных требуются блокировки, так как они имеют общее изменяемое глобальное состояние. Вот обратный список для приведенного выше списка приоритетов блокировки. В него не включены конечные ресурсы 1-го уровня, так как они слишком просты.

Модификации MethodTable (def, cache): MethodTable->writelock

Объявления типов: блокировка toplevel

Применение типов: блокировка typecache

Таблицы глобальных переменных: Module->lock

Сериализатор модуля: блокировка toplevel

JIT и вывод типов: блокировка генерации кода

Обновления MethodInstance/CodeInstance: Method->writelock

  • Эти устанавливаются при создании и являются неизменяемыми:

    • specTypes

    • sparam_vals

    • def

    • owner

  • Указатели функций:

    • выполняют переход один раз от NULL к значению, которое согласовывается внутри JIT

Метод: Method->writelock

  • массив корней (сериализатор и генерация кода)

  • вызов/специализации/модификации tfunc