Proper maintenance and care of multi-threading locks
以下策略用于确保代码无死锁(通常通过解决第四个Coffman条件:循环等待)。
- 结构代码,使得一次只需要获取一个锁。
- 始终按照下表所示的顺序获取共享锁。
- 避免构造那些需要不受限制递归的情况
Locks
以下是系统中存在的所有锁以及避免死锁潜在风险的使用机制(此处不允许使用鸵鸟算法):
以下绝对是叶锁(级别 1),并且不得尝试获取任何其他锁:
安全点
请注意,此锁是通过
JL_LOCK
和JL_UNLOCK
隐式获取的。使用_NOGC
变体可以避免对级别 1 锁的影响。在持有此锁时,代码不得进行任何分配或触发任何安全点。请注意,在进行分配、启用/禁用垃圾回收、进入/恢复异常帧以及获取/释放锁时会出现安全点。
共享地图
最终处理器
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 锁(安全点):
- 全球根锁
- 模块->锁
- JLDebuginfoPlugin::插件互斥锁
- 新推断的互斥锁
以下是一个三级锁,只能在内部获取一级或二级锁:
- 方法->写锁
- 类型缓存
以下是一个4级锁,只能递归获取1级、2级或3级锁:
- MethodTable->写锁
在此点以上持有锁时,不得调用任何 Julia 代码。
orc::ThreadSafeContext (TSCtx) 锁在锁定层次结构中占据特殊位置。它们用于保护 LLVM 的全局非线程安全状态,但可以有任意数量的这些锁。默认情况下,所有这些锁在与其余层次结构比较时可以视为级别 5 锁。获取 TSCtx 应仅从 JIT 的 TSCtx 池中进行,并且在将其返回到池之前,必须释放该 TSCtx 上的所有锁。如果必须同时获取多个 TSCtx 锁(由于递归编译),则应按照从池中借用 TSCtx 的顺序获取锁。
以下是一个5级锁
- JuliaOJIT::发射互斥量
以下是一个6级锁,只能递归获取较低级别的锁:
- 代码生成
- jl模块互斥锁
以下是一个几乎是根锁(级别结束-1),这意味着在尝试获取它时只能保持根锁:
typeinf
这可能是最棘手的之一,因为类型推断可以从多个点调用。
目前锁与代码生成锁合并,因为它们是递归调用彼此的。
以下锁用于同步IO操作。请注意,在持有上述任何其他锁的情况下进行任何I/O(例如,打印警告消息或调试信息)可能会导致难以发现的严重死锁。请务必小心!
iolock
个别线程同步器锁
这可能会在释放 iolock 后继续进行,或者在没有它的情况下获得,但请务必小心,切勿在持有 iolock 的情况下尝试获取它。
Libdl.LazyLibrary 锁定
以下是根锁,这意味着在尝试获取它时不应持有其他锁:
顶级
这应该在尝试顶级操作时进行(例如创建新类型或定义新方法):在分阶段函数内部尝试获取此锁将导致死锁条件!
此外,目前尚不清楚任何代码是否可以安全地与任意顶层表达式并行运行,因此可能需要所有线程首先到达安全点。
Broken Locks
以下锁具已损坏:
顶级
目前不存在
修复:创建它
模块->锁
这容易导致死锁,因为无法确定它是按顺序获取的。一些操作(例如
import_module
)缺少锁。修复:替换为
jl_modules_mutex
?loading.jl:
require
和register_root_module
此文件可能存在许多问题。
修复:需要锁定
Shared Global Data Structures
这些数据结构由于是共享的可变全局状态,因此每个都需要锁。它是上述锁优先级列表的反向列表。该列表不包括级别 1 的叶资源,因为它们很简单。
方法表修改(def,cache):MethodTable->写锁
类型声明:顶级锁
类型应用:类型缓存锁
全局变量表:模块->锁定
模块序列化器:顶级锁
JIT 和类型推断:代码生成锁
MethodInstance/CodeInstance 更新 : Method->写锁, 代码生成锁
- 这些在构建时设置并且不可变:
- specTypes
- sparam_vals
- def
- 所有者
- 这些是由
jl_type_infer
设置的(在持有代码生成锁时):
- 缓存
- rettype
- 推断的
* valid ages
inInference
标志:
- 优化以快速避免在
jl_type_infer
正在运行时再次进入该函数- 实际状态(设置
inferred
,然后fptr
)受到代码生成锁的保护
函数指针:
- 这些过渡只发生一次,从
NULL
变为一个值,同时保持代码生成锁。代码生成器缓存(
functionObjectsDecls
的内容):
- 这些可以多次转换,但仅在持有代码生成锁时。
- 使用旧版本是有效的,或者对新版本进行阻塞,因此竞争是良性的,只要代码小心不要引用方法实例中的其他数据(例如
rettype
),并假设它是协调的,除非同时持有代码生成锁。
LLVMContext : 代码生成锁
方法 : 方法->写锁
- 根数组(序列化器和代码生成)
- 调用 / 专业化 / tfunc 修改