Proper maintenance and care of multi-threading locks

以下策略用于确保代码无死锁(通常通过解决第四个Coffman条件:循环等待)。

  1. 结构代码,使得一次只需要获取一个锁。
  2. 始终按照下表所示的顺序获取共享锁。
  3. 避免构造那些需要不受限制递归的情况

Locks

以下是系统中存在的所有锁以及避免死锁潜在风险的使用机制(此处不允许使用鸵鸟算法):

以下绝对是叶锁(级别 1),并且不得尝试获取任何其他锁:

  • 安全点

    请注意,此锁是通过 JL_LOCKJL_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: requireregister_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 修改