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