Надлежащее обслуживание многопоточных блокировок
Следующие стратегии гарантируют отсутствие взаимоблокировок в коде (как правило, путем соблюдения 4-го условия Коффмана: циклического ожидания).
Код нужно структурировать таким образом, чтобы за один раз нужно было получать только одну блокировку.
Общие блокировки всегда следует получать в том же порядке, как указано в таблице ниже.
Необходимо избегать конструкций, в которых ожидается, что потребуется неограниченная рекурсия.
Блокировки
Ниже приведены все блокировки, существующие в системе, а также механизмы их использования, позволяющие избежать потенциальных взаимоблокировок (алгоритм страуса здесь недопустим).
Следующие блокировки, безусловно, являются конечными блокировками (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