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

Следующие стратегии гарантируют отсутствие взаимоблокировок в коде (как правило, путем соблюдения 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

  • jllockedstream::mutex

  • debuginfo_asyncsafe

  • inferencetimingmutex

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

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

  • typecache

  • Module->lock

  • JLDebuginfoPlugin::PluginMutex

  • newlyinferredmutex

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

  • Method->writelock

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

  • MethodTable->writelock

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

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

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

  • codegen

  • jl_modules_mutex

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

  • typeinf

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

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

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

  • iolock

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

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

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

  • 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

  • Эти задаются с помощью jl_type_infer (при удержании блокировки генерации кода):

    • cache

    • rettype

    • inferred

    * допустимый возраст
  • Флаг inInference:

    • Оптимизация для быстрого предотвращения повторения в функции jl_type_infer, когда она уже запущена

    • Фактическое состояние (установки inferred, затем fptr) защищено блокировкой генерации кода

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

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

  • Кеш генератора кода (содержимое functionObjectsDecls):

    • может выполнять переход несколько раз, но только пока удерживается блокировка генерации кода

    • Можно использовать его старую версию или блокировать новые версии, так что гонки не опасны, если только код не ссылается на другие данные в экземпляре метода (например, rettype) и предполагает, что они согласованы, если только не удерживает блокировку генерации кода

LLVMContext: блокировка генерации кода

Метод: Method->writelock

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

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