Proper maintenance and care of multi-threading locks
Следующие стратегии используются для обеспечения того, чтобы код был свободен от взаимных блокировок (в основном, путем устранения 4-го условия Кофмана: кругового ожидания).
- структурируйте код так, чтобы только один замок нужно было захватить за раз
- всегда получайте совместимые блокировки в одном и том же порядке, как указано в таблице ниже
- избегайте конструкций, которые предполагают необходимость неограниченной рекурсии
Locks
Ниже приведены все замки, которые существуют в системе, и механизмы их использования, которые избегают потенциальных взаимных блокировок (алгоритм страуса здесь не допускается):
Следующие определенно являются листовыми блокировками (уровень 1) и не должны пытаться захватывать какие-либо другие блокировки:
safepoint
Обратите внимание, что этот замок захватывается неявно с помощью
JL_LOCK
иJL_UNLOCK
. Используйте варианты_NOGC
, чтобы избежать этого для замков уровня 1.Держась за этот замок, код не должен выполнять никаких аллокаций или попадать в любые точки безопасности. Обратите внимание, что точки безопасности возникают при выполнении аллокаций, включении / отключении сборки мусора, входе / восстановлении кадров исключений и захвате / освобождении замков.
shared_map
финализаторы
pagealloc
gcpermlock
flisp
jlinstackwalk (Win32)
ResourcePool<?>::mutex
RLST_mutex
llvmпечатающиймьютекс
jlзаблокированныйпоток::мьютекс
debuginfo_asyncsafe
инференциявремямьютекс
ExecutionEngine::SessionLock
flisp сам по себе уже является потокобезопасным, этот замок только защищает пул
jl_ast_context_list_t
, аналогично, мьютексы ResourcePool<?>::mutexes просто защищают связанный пул ресурсов.
Следующее - это блокировка листа (уровень 2), и она только внутренне захватывает блокировки уровня 1 (safepoint):
- глобальныекорнизамок
- Модуль->блокировка
- JLDebuginfoPlugin::PluginMutex
- newlyinferredmutex
Следующий уровень 3 блокировки, который может получать только внутренние блокировки уровня 1 или уровня 2:
- Метод->writelock
- typecache
Следующий уровень 4 блокировки, который может рекурсивно получать блокировки уровня 1, 2 или 3:
- MethodTable->writelock
Никакой код Julia не может быть вызван, пока удерживается блокировка выше этой точки.
orc::ThreadSafeContext (TSCtx) блокировки занимают особое место в иерархии блокировок. Они используются для защиты глобального небезопасного состояния LLVM, но их может быть произвольное количество. По умолчанию все эти блокировки могут рассматриваться как блокировки уровня 5 для целей сравнения с остальной иерархией. Получение TSCtx должно осуществляться только из пула TSCtx JIT, и все блокировки на этом TSCtx должны быть освобождены перед его возвратом в пул. Если несколько блокировок TSCtx необходимо получить одновременно (из-за рекурсивной компиляции), то блокировки должны быть получены в порядке, в котором TSCtx были заимствованы из пула.
Следующий уровень - 5 замок
- JuliaOJIT::EmissionMutex
Следующее - это замок уровня 6, который может рекурсивно получать замки только на более низких уровнях:
- кодогенерация
- jlмодулимьютекс
Следующее является почти блокировкой корня (уровень end-1), что означает, что только блокировка корня может быть удержана при попытке ее захватить:
typeinf
это, возможно, один из самых сложных случаев, поскольку вывод типов может быть вызван из многих точек
в настоящее время блокировка объединена с блокировкой кодогенерации, так как они вызывают друг друга рекурсивно
Следующий замок синхронизирует операции ввода-вывода. Имейте в виду, что выполнение любых операций ввода-вывода (например, печать предупреждающих сообщений или отладочной информации) при удержании любого другого замка, перечисленного выше, может привести к вредоносным и труднонаходимым взаимным блокировкам. БУДЬТЕ ОЧЕНЬ ОСТОРОЖНЫ!
iolock
Индивидуальные блокировки ThreadSynchronizers
это может продолжаться после освобождения iolock или быть получено без него, но будьте очень осторожны и никогда не пытайтесь получить iolock, держа его в руках
Libdl.LazyLibrary блокировка
Следующее является корневым замком, что означает, что никакой другой замок не должен удерживаться при попытке его захвата:
toplevel
это должно быть выполнено при попытке выполнить действие верхнего уровня (например, создать новый тип или определить новый метод): попытка получить этот замок внутри функции с этапами приведет к состоянию взаимной блокировки!
дополнительно, неясно, может ли любой код безопасно выполняться параллельно с произвольным верхним уровнем выражения, поэтому может потребоваться, чтобы все потоки сначала достигли безопасной точки
Broken Locks
Следующие замки сломаны:
toplevel
в данный момент не существует
исправить: создать это
Модуль->блокировка
Это уязвимо к взаимным блокировкам, так как нельзя быть уверенным, что оно захвачено в последовательности. Некоторые операции (такие как
import_module
) не имеют блокировки.исправить: заменить на
jl_modules_mutex
?loading.jl:
require
иregister_root_module
Этот файл потенциально имеет множество проблем.
исправить: нужны замки
Shared Global Data Structures
Эти структуры данных требуют блокировок из-за того, что они являются общим изменяемым глобальным состоянием. Это обратный список приоритетов блокировок для вышеупомянутого списка. Этот список не включает ресурсы уровня 1, так как они просты.
Методы изменения MethodTable (def, cache) : MethodTable->writelock
Объявления типов: верхний уровень блокировки
Тип приложения: блокировка кэша типов
Глобальные переменные таблицы : Модуль->замок
Модуль сериализатора: верхний уровень блокировки
JIT и вывод типов: блокировка генерации кода
MethodInstance/CodeInstance обновления : Method->writelock, блокировка кодогенерации
Эти значения устанавливаются при строительстве и являются неизменяемыми:
- specTypes
- sparam_vals
- def
- владелец
Эти устанавливаются
jl_type_infer
(при удерживании блокировки кодогенерации):
- кэш
- rettype
- выведенный
* valid ages
inInference
флаг:
- оптимизация для быстрого избежания повторного входа в
jl_type_infer
, пока он уже выполняется- фактическое состояние (установки
inferred
, затемfptr
) защищено блокировкой кодогенерации
Указатели на функции:
- эти переходы происходят один раз, от
NULL
к значению, пока удерживается блокировка генерации кодаКэш генератора кода (содержимое
functionObjectsDecls
):
- эти могут переходить несколько раз, но только пока удерживается блокировка кодогенерации
- это допустимо использовать старую версию этого, или блокировать новые версии этого, так что гонки безвредны, если только код осторожен и не ссылается на другие данные в экземпляре метода (таких как
rettype
) и не предполагает, что они согласованы, если также не удерживается блокировка кодогенерации
LLVMContext : блокировка кодогенерации
Метод : Method->writelock
- массив корней (сериализатор и кодогенерация)
- вызов / специализации / изменения tfunc