并发

仓颉线程

仓颉编程语言提供抢占式的并发模型,其中仓颉线程是基本的执行单元。仓颉线程由仓颉运行时自行管理并非是底层 native 线程(如操作系统线程)。在无歧义的情况下,我们直接使用“线程”一词作为“仓颉线程”的简称。每个线程都具有如下性质:

  • 每个线程在执行的任意时刻都可能被另一个线程抢占;
  • 多个线程之间并发执行;
  • 当线程发生阻塞时,该线程将被挂起;
  • 不同线程之间可以共享内存(需要显式同步)。

仓颉程序执行从初始化全局变量开始,然后调用程序入口 main。当 main 退出时,整个程序执行便退出而不会等待其余线程执行完毕。

创建线程

通过 spawn 关键字、一个可缺省的 ThreadContext 类型入参(关于 ThreadContext 的介绍见下方)和一个不包含形参的 lambda 可以创建并启动一个线程,同时该表达式返回一个 Future<T> 的实例(关于 Future<T> 的介绍见下方 15.1.2)。spawn 表达式的 BNF 如下:

spawnExpression
    : 'spawn' ( '(' expression ')' )? trailingLambdaExpression
    ;

其中 spawn 的可选参数的类型为 ThreadContext。以下是 spawn 表达式缺省 ThreadContext 类型入参的一个示例。

func add(a: Int32, b: Int32): Int32 {
    println("This is a new thread")
    return a + b
}

main(): Int64 {
    println("This is main")

    // Create a thread.
    let fut: Future<Int32> = spawn {
        add(1, 2)
    }

    println("main waiting...")
    // Waiting for the results of thread execution.
    let res: Int32 = fut.get()
    // Print the result.
    println("1 + 2 = ${res}")
}

// OUTPUT maybe:
// This is main
// main waiting...
// This is a new thread
// 1 + 2 = 3

执行 spawn 表达式的过程中,闭包会被封装成一个运行时任务并提交给调度器,然后由调度器的策略决定任务的执行时机。一旦 spawn 表达式执行完成,任务对应的线程便处于可执行状态。需要注意的是,spawn 表达式的闭包中不可捕获 var 声明的局部变量;关于闭包,详见[闭包]。

Future<T> 泛型类

spawn 表达式的返回值是一个 Future<T> 对象可用于获取线程的计算结果。其中,T 的类型取决于 spawn 表达式中的闭包的返回值类型。

func foo(): Int64 {...}
func bar(): String {...}

// The return type of foo() is Int64.
let f1: Future<Int64> = spawn {
    foo()
}

// The return type of bar() is String.
let f2: Future<String> = spawn {
    bar()
}

// Waiting for the threads' execution results.
let r1: Int64 = f1.get()
let r2: String = f2.get()

一个Future<T>对象代表一个未完成的计算或任务。Future<T> 是一个泛型类,其定义如下:

class Future<T> {
    ... ...

    /**
     * Blocking the current thread,
     * waiting for the result of the thread corresponding to this Future<T> object.
     * @throws Exception or Error if an exception occurs in the corresponding thread.
     */
    func get(): T

    /**
     * Blocking the current thread,
     * waiting for the result of the thread corresponding to this Future<T> object.
     * If the corresponding thread has not completed execution within `ns` nanoseconds,
     * the method will return `Option<T>.None`.
     * If `ns <= 0`, same as `get()`.
     * @throws Exception or Error if an exception occurs in the corresponding thread.
     */
    func get(ns: Int64): Option<T>

    /**
     * Non-blocking method that immediately returns None if thread has not finished execution.
     * Returns the computed result otherwise.
     * @throws Exception or Error if an exception occurs in the corresponding thread.
     */
    func tryGet(): Option<T>

    // Get the associated thread object.
    prop thread: Thread
}

如果线程由于发生异常而终止,那么它会将异常传递到 Future<T> 对象,并在终止前默认打印出异常信息。当线程对应的 Future<T> 对象调用 get() 时,异常将被再次抛出。例如:

main(args:Array<String>) {
    let fut: Future<Int64> = spawn {
        if (args.size != -1) {
            throw IllegalArgumentException()
        }
        return 100
    }
    try {
        let res: Int64 = fut.get()
        print("val = ${res}\n")
    } catch (e: IllegalArgumentException) {
        print("Exception occured\n")
    }
}
// OUTPUT:
// An exception has occurred:
// IllegalArgumentException
//     ...
// Exception occurred

Thread 类

每个 Future<T> 对象都有一个关联的线程对象,其类型为 Thread,可通过 Future<T>thread 属性获取。线程类 Thread 主要用于获取线程的相关信息,例如线程标识等,其部分定义如下。

class Thread {
    ... ...
    // Get the currently running thread
    static prop currentThread: Thread

    // Get the unique identifier (represented as an integer) of the thread object
    prop id: Int64

    // Get or set the name of the thread
    mut prop name: String
}

静态属性 currentThread 可获取当前正在执行的线程对象。下面的示例代码能够打印出当前执行线程的标识。注意:线程在每次执行时可能被赋予不同的线程标识。

main() {
    let fut: Future<Unit> = spawn {
        let tid = Thread.currentThread.id
        println("New thread id: ${tid}")
    }

    fut.get()
}
// OUTPUT:
// New thread id: 2

线程睡眠

在仓颉标准库的 sync 包中提供了sleep 函数能够让线程睡眠指定时长。如果睡眠时长为零,即参数时长为 duration.Zero, 那么当前线程只会让出执行资源并不会进入睡眠。

func sleep(duration: Duration)

线程终止

在正常执行过程中,若线程对应的闭包执行完成,则该线程执行结束。此外,还可以通过 Future<T> 对象的 cancel() 方法向对应的线程发送终止请求,这一方法仅发送请求而不会影响线程的执行。线程类的 hasPendingCancellation 属性用于检查当前执行的线程是否存在终止请求,程序员可以通过该检查来自行决定是否提前终止线程以及如何终止线程。

class Future<T> {
    ... ...
    // Send a termination request to its executing thread.
    public func cancel(): Unit
}

class Thread {
    ... ...
    // Check whether the thread has any cancellation request
    prop hasPendingCancellation: Bool
}

以下示例展示如何终止线程。

main(): Unit {
    let future = spawn { // Create a new thread
        while (true) {
            //...
            if (Thread.currentThread.hasPendingCancellation) {
                return   // Terminate when having a request
            }
            //...
        }
    }
    //...
    future.cancel()    // Send a termination request
    future.get() // Wait for thread termination
}

线程上下文

线程总是被运行在特定的上下文里,线程上下文会影响线程运行时的行为。用户创建线程时,除了缺省 spawn 表达式入参,也可以通过传入不同 ThreadContext 类型的实例,选择不同的线程上下文,然后一定程度上控制并发行为。

接口 ThreadContext

ThreadContext 接口类型定义如下:

  • 结束方法 end 用于向当前 context 发送结束请求(关于线程上下文结束的介绍见下方 thread context 结束)。
  • 检查方法 hasEnded 用于检查当前 context 是否已结束。
interface ThreadContext {
    func end(): Unit
    func hasEnded(): Bool
}

目前不允许用户自行实现 ThreadContext 接口,仓颉语言根据使用场景,提供了如下线程上下文类型:

  • MainThreadContext: 用户可以在终端应用开发中使用此线程上下文;

具体定义可在终端框架库中查阅。

线程上下文关闭

当关闭 ThreadContext 对象时,可以通过 ThreadContext 对象提供的 end 方法向对应的 context 发送关闭请求。此方法向运行在此 context 上的所有线程发送终止请求(参见线程终止),并马上返回,运行时待所有绑定至此 context 上的仓颉 thread 运行结束后,关闭 context。

同样的,关闭请求不会影响在此 context 上所有线程的执行,用户可以通过 hasPendingCancellation 检查来自行决定是否提前终止线程以及如何终止线程。

线程局部变量

类型 ThreadLocal<T> 可用于定义线程局部变量。和普通变量相比,线程局部变量有不同的访问语义。当多个线程共享使用同一线程局部变量时,每个线程都有各自的一份值拷贝。线程对变量的访问会读写线程本地的值,而不会影响其他线程中变量的值。因而,ThreadLocal<T> 类是线程安全的。类 ThreadLocal<T> 如下定义所示,其中:

  • 构造方法 init 用于构造线程局部变量。在构造完但未设置变量值时,线程对变量的读取将得到空值 None
  • 读写方法 get/set 用于访问线程局部变量的值。当线程将变量的值设为 None 后,当前线程中的变量值将被清除。
class ThreadLocal<T> {
    // Construct a thread-local variable contains None.
    public init()

    // Get the value of the thread-local variable of the current executing thread.
    public func get(): ?T

    // Set a value to the thread-local variable.
    public func set(value: ?T): Unit
}

以下示例展示如何使用线程局部变量。

let tlv = ThreadLocal<Int64>() // Define a thread-local variable

main(): Unit {
    for (i in 0..3) { // Spawn three threads
        spawn {
            tlv.set(i) // Each thread sets a different value
            // ...
            println("${tlv.get()}") // Each thread prints its own value
        }
    }
    // ...
    println("${tlv.get()}") // Print `None`
                            // since the current thread does not set any value.
}

同步机制

原子操作

原子操作确保指令被原子地执行,即执行过程中不会被中断。对原子变量的一个写操作,对于后续对同一个原子变量的读操作来说,总是可见的。此外,原子操作是非阻塞的动作,不会导致线程阻塞。仓颉语言提供了针对整数类型(包括 Int8Int16Int32Int64UInt8UInt16UInt32UInt64),布尔类型(Bool)和引用类型的原子操作。

  • 对于整数类型,我们提供基本的读写、交换以及算术运算的操作:
    • load:读取
    • store:写入
    • swap:交换
    • compareAndSwap:比较再交换
    • fetchAdd:加法
    • fetchSub:减法
    • fetchAnd:与
    • fetchOr:或
    • fetchXor:异或
// Signed Integers.
class AtomicInt8 {
    ... ...
    init(val: Int8)

    public func load(): Int8
    public func store(val: Int8): Unit
    public func swap(val: Int8): Int8
    public func compareAndSwap(old: Int8, new: Int8): Bool

    public func fetchAdd(val: Int8): Int8
    public func fetchSub(val: Int8): Int8
    public func fetchAnd(val: Int8): Int8
    public func fetchOr(val: Int8): Int8
    public func fetchXor(val: Int8): Int8

    ... ... // Operator overloading, etc.
}

class AtomicInt16 {...}
class AtomicInt32 {...}
class AtomicInt64 {...}

// Unsigned Integers.
class AtomicUInt8  {...}
class AtomicUInt16 {...}
class AtomicUInt32 {...}
class AtomicUInt64 {...}
  • 对于布尔类型,仅提供基本的读写、交换操作,不提供算术运算的操作:
    • load:读取
    • store:写入
    • swap:交换
    • compareAndSwap:比较再交换
// Boolean.
class AtomicBool {
    ... ...
    init(val: Bool)

    public func load(): Bool
    public func store(val: Bool): Unit
    public func swap(val: Bool): Bool
    public func compareAndSwap(old: Bool, new: Bool): Bool

    ... ... // Operator overloading, etc.
}
  • 对于引用类型,仅提供基本的读写、交换操作,不提供算术运算的操作:
    • load:读取
    • store:写入
    • swap:交换
    • compareAndSwap:比较再交换
class AtomicReference<T> where T <: Object {
    ... ...
    init(val: T)

    public func load(): T
    public func store(val: T): Unit
    public func swap(val: T): T
    public func compareAndSwap(old: T, new: T): Bool
}
  • 此外,对于可空引用类型,可以通过 AtomicOptionReference 保存“空引用”(以 None 表示)。
class AtomicOptionReference<T> where T <: Object {
    public init(val: Option(T))
    public func load(): Option<T>
    public func store(val: Option<T>): Unit
    public func swap(val: Option<T>): Option<T>
    public func compareAndSwap(old: Option<T>, new: Option<T>): Bool
}

以上方法均包含一个隐藏参数 memory order。当前只支持 Sequential Consistency 内存模型,后续可以考虑增加更宽松的内存模型,例如 acquire/release 内存顺序等。

IllegalSynchronizationStateException

IllegalSynchronizationStateException 是一个运行时异常,当出现违规行为,例如当线程尝试去释放当前线程未获取的互斥量的时候,内置的并发原语会抛出此异常。

IReentrantMutex

IReentrantMutex 是可重入式互斥并发原语的公共接口,提供如下三个方法,需要开发者提供具体实现:

  • lock(): Unit:获取锁,如果获取不到,阻塞当前线程
  • tryLock(): Bool:尝试获取锁
  • unlock(): Unit:释放锁
interface IReentrantMutex {
    // Locks the mutex, blocks the current thread if the mutex is not available.
    public func lock(): Unit

    // Tries to lock the mutex, returns false if the mutex is not available, otherwise locks the mutex and returns true.
    public func tryLock(): Bool

    // Unlocks the mutex. If the mutex was locked repeatedly N times, this method should be invoked N times to
    // fully unlock the mutex. When the mutex is fully unlocked, unblocks one of the threads waiting on its `lock`
    // (no particular admission policy implied).
    // Throws ISSE("Mutex is not locked by the current thread") if the current thread does not hold this mutex.
    public func unlock(): Unit
}

注意:

  1. 开发者在实现该接口时需要保证底层互斥锁确实支持嵌套锁。
  2. 开发者在实现该接口时需要保证在出现锁状态异常时抛出 IllegalSynchronizationStateException

ReentrantMutex

ReentrantMutex 提供如下方法:

  • lock(): Unit:获取锁,如果获取不到,阻塞当前线程
  • tryLock(): Bool:尝试获取锁
  • unlock(): Unit:释放锁

ReentrantMutex 是内置的锁,在同一时刻最多只能被一个线程持有。如果给定的 ReentrantMutex 已经被另外一个线程持有,lock 方法会阻塞当前线程直到该互斥锁被释放,而 tryLock 方法会立即返回 falseReentrantMutex 是可重入锁,即如果一个线程尝试去获取一个它已经持有的 ReentrantMutex 锁,他会立即获取该 ReentrantMutex。为了成功释放该锁,unlock 的调用次数必须与 lock 的调用次数相匹配。

注意:ReentrantMutex 是内置的互斥锁,不允许开发者继承。

// Base class for built-in reentrant mutual exclusion concurrency primitives.
sealed class ReentrantMutex <: IReentrantMutex {
    // Constructor.
    init()

    // Locks the mutex, blocks current thread if the mutex is not available.
    public func lock(): Unit

    // Tries to lock the mutex, returns false if the mutex is not available, otherwise locks the mutex and returns true.
    public func tryLock(): Bool

    // Unlocks the mutex. If the mutex was locked repeatedly N times, this method should be invoked N times to
    // fully unlock the mutex. Once the mutex is fully unlocked, unblocks one of the threads waiting in its `lock` method, if any
    // (no particular admission policy implied).
    // Throws ISSE("Mutex is not locked by the current thread") if the current thread does not hold this mutex.
    public func unlock(): Unit
}

synchronized

通过 synchronized 关键字和一个 ReentrantMutex 对象,对其后面修饰的代码块进行保护,使得同一时间只允许一个线程执行里面的代码。ReentrantMutex 或其派生类的实例可以作为参数传递给 synchronized 关键字,这将导致如下转换:

//=======================================
// `synchronized` expression
//=======================================
let m: ReentrantMutex = ...
synchronized(m) {
    foo()
}
//=======================================
// is equivalent to the following program.
//=======================================
let m: ReentrantMutex = ...
m.lock()
try {
    foo()
} finally {
    m.unlock()
}

注意:synchronized 关键字与 IReentrantMutex 接口不兼容。

  • 一个线程在进入 synchronized 修饰的代码块之前,必须先获取该 ReentrantMutex 对象的锁,如果无法获取该锁,则当前线程被阻塞;
  • 一个线程在退出 synchronized 修饰的代码块之后,会自动释放该 ReentrantMutex 对象的锁;只会执行一个unlock操作,因此允许嵌套同一个互斥锁的synchronized代码块。
  • 在到达 synchronized 修饰的代码块中的 return e 表达式后,会先将 e 求值到 v,再调用 m.unlock(),最后返回 v
  • synchronized 修饰的代码块内有 breakcontinue 表达式,并且该表达式的执行会导致程序跳出该代码块,那么会自动调用 m.unlock() 方法。
  • 当在 synchronized 修饰的代码块中发生异常并跳出该代码块时,会自动调用 m.unlock() 方法。

synchronized 表达式的 BNF 如下:

synchronizedExpression
    : 'synchronized' '(' expression ')' block
    ;

其中 expression 是一个 ReentrantMutex 的对象。

Monitor

Monitor 是一个内置的数据结构,它绑定了互斥锁和单个与之相关的条件变量(也就是等待队列)。Monitor 可以使线程阻塞并等待来自另一个线程的信号以恢复执行。这是一种利用共享变量进行线程同步的机制,主要提供如下方法:

  • wait(timeout!: Duration = Duration.Max): Bool:等待信号,阻塞当前线程
  • notify(): Unit:唤醒一个在 Monitor 上等待的线程(如果有)
  • notifyAll(): Unit:唤醒所有在 Monitor 上等待的线程(如果有)

调用 Monitor 对象的 waitnotifynotifyAll 方法前,需要确保当前线程已经持有对应的 Monitor 锁。wait 方法包含如下动作:

  1. 添加当前线程到该 Monitor 对应的等待队列中
  2. 阻塞当前线程,同时完全释放Monitor 锁,并记录获取次数
  3. 等待某个其它线程使用同一个 Monitor 实例的 notifynotifyAll 方法向该线程发出信号
  4. 唤醒当前线程并同时获取 Monitor 锁,恢复第二步记录的获取次数

注意wait 方法接受一个可选参数 timeout。需要注意的是,业界很多常用的常规操作系统不保证调度的实时性,因此无法保证一个线程会被阻塞“精确时长”——即可能会观察到由系统导致的不精确情况。此外,当前语言规范明确允许实现中发生虚假唤醒——在这种情况下,wait 返回值是由实现决定的——可能为 true 或 false。因此鼓励开发者始终将 wait 包在一个循环中:

synchronized (obj) {
    while (<condition is not true>) {
        obj.wait();
    }
}

Monitor 定义如下:

class Monitor <: ReentrantMutex {
    // Constructor.
    init()

    // Blocks until either a paired `notify` is invoked or `timeout` pass.
    // Returns `true` if the monitor was signalled by another thread or `false` on timeout. Spurious wakeups are allowed.
    // Throws ISSE("Mutex is not locked by the current thread") if the current thread does not hold this mutex.
    func wait(timeout!: Duration = Duration.Max): Bool

    // Wakes up a single thread waiting on this monitor, if any (no particular admission policy implied).
    // Throws ISSE("Mutex is not locked by the current thread") if the current thread does not hold this mutex.
    func notify(): Unit

    // Wakes up all threads waiting on this monitor, if any (no particular admission policy implied).
    // Throws ISSE("Mutex is not locked by the current thread") if the current thread does not hold this mutex.
    func notifyAll(): Unit
}

MultiConditionMonitor

MultiConditionMonitor 是一个内置的数据结构,它绑定了互斥锁和一组与之相关的动态创建的条件变量。该类应仅当在 Monitor 类不足以实现高级并发算法时被使用。

提供如下方法:

  • newCondition(): ConditionID:创建一个新的等待队列并与当前对象关联,返回一个特定的ConditionID标识符
  • wait(id: ConditionID, timeout!: Duration = Duration.Max): Bool:等待信号,阻塞当前线程
  • notify(id: ConditionID): Unit:唤醒一个在 Monitor 上等待的线程(如果有)
  • notifyAll(id: ConditionID): Unit:唤醒所有在 Monitor 上等待的线程(如果有)

初始化时,MultiConditionMonitor 没有与之相关的 ConditionID 实例。每次调用 newCondition 都会将创建一个新的等待队列并与当前对象关联,并返回如下类型作为唯一标识符:

external struct ConditionID {
   private init() { ... } // constructor is intentionally private to prevent
                          // creation of such structs outside of MultiConditionMonitor
}

请注意使用者不可以将一个 MultiConditionMonitor 实例返回的 ConditionID 传给其它实例,或者手动创建 ConditionID(例如使用 unsafe)。由于 ConditionID 所包含的数据(例如内部数组的索引,内部队列的直接地址,或任何其他类型数据等)和创建它的 MultiConditionMonitor 相关,所以将“外部” conditonID 传入 MultiConditionMonitor 中会导致 IllegalSynchronizationStateException

class MultiConditionMonitor <: ReentrantMutex {
    // Constructor.
    init()

    // Returns a new ConditionID associated with this monitor. May be used to implement
    // "single mutex -- multiple wait queues" concurrent primitives.
    // Throws ISSE("Mutex is not locked by the current thread") if the current thread does not hold this mutex.
    func newCondition(): ConditionID

    // Blocks until either a paired `notify` is invoked or `timeout` pass.
    // Returns `true` if the specified condition was signalled by another thread or `false` on timeout.
    // Spurious wakeups are allowed.
    // Throws ISSE("Mutex is not locked by the current thread") if the current thread does not hold this mutex.
    // Throws ISSE("Invalid condition") if `id` was not returned by `newCondition` of this MultiConditionMonitor instance.
    func wait(id: ConditionID, timeout!: Duration = Duration.Max): Bool

    // Wakes up a single thread waiting on the specified condition, if any (no particular admission policy implied).
    // Throws ISSE("Mutex is not locked by the current thread") if the current thread does not hold this mutex.
    // Throws ISSE("Invalid condition") if `id` was not returned by `newCondition` of this MultiConditionMonitor instance.
    func notify(id: ConditionID): Unit

    // Wakes up all threads waiting on the specified condition, if any (no particular admission policy implied).
    // Throws ISSE("Mutex is not locked by the current thread") if the current thread does not hold this mutex.
    // Throws ISSE("Invalid condition") if `id` was not returned by `newCondition` of this MultiConditionMonitor instance.
    func notifyAll(id: ConditionID): Unit
}

**示例:**使用 MultiConditionMonitor 去实现一个“长度固定的有界 FIFO 队列”,当队列为空,get() 会被阻塞;当队列满了时,put() 会被阻塞。

class BoundedQueue {
    // Create a MultiConditionMonitor, two Conditions.
    let m: MultiConditionMonitor = MultiConditionMonitor()
    var notFull: ConditionID
    var notEmpty: ConditionID

    var count: Int64 // Object count in buffer.
    var head: Int64  // Write index.
    var tail: Int64  // Read index.

    // Queue's length is 100.
    let items: Array<Object> = Array<Object>(100, {i => Object()})

    init() {
        count = 0
        head = 0
        tail = 0

        synchronized(m) {
          notFull  = m.newCondition()
          notEmpty = m.newCondition()
        }
    }

    // Insert an object, if the queue is full, block the current thread.
    public func put(x: Object) {
        // Acquire the mutex.
        synchronized(m) {
          while (count == 100) {
            // If the queue is full, wait for the "queue notFull" event.
            m.wait(notFull)
          }
          items[head] = x
          head++
          if (head == 100) {
            head = 0
          }
          count++

          // An object has been inserted and the current queue is no longer
          // empty, so wake up the thread previously blocked on get()
          // because the queue was empty.
          m.notify(notEmpty)
        } // Release the mutex.
    }

    // Pop an object, if the queue is empty, block the current thread.
    public func get(): Object {
        // Acquire the mutex.
        synchronized(m) {
          while (count == 0) {
            // If the queue is empty, wait for the "queue notEmpty" event.
            m.wait(notEmpty)
          }
          let x: Object = items[tail]
          tail++
          if (tail == 100) {
            tail = 0
          }
          count--

          // An object has been popped and the current queue is no longer
          // full, so wake up the thread previously blocked on put()
          // because the queue was full.
          m.notify(notFull)

          return x
        } // Release the mutex.
    }
}

内存模型

内存模型主要解决并发编程中内存可见性的问题,它规定了一个线程对一个变量的写操作,何时一定可以被其他线程对同一变量的读操作观测到。

  • 如果存在数据竞争,那么行为是未定义的。
  • 如果没有数据竞争,一个读操作所读到的值是:在 happens-before 顺序上离它最近的一个写操作所写入的值。

主要解决并发编程中内存可见性的问题,即一个线程的写操作何时会对另一个线程可见。

数据竞争 Data Race

如果两个线程对 同一个数据 访问,其中至少有一个是写操作,而且这两个操作之间没有 happens-before 关系(在 15.4.2 节定义),那么形成一个数据竞争。

对“同一个数据访问”的定义:

  1. 对同一个 primitive type、enum、array 类型的变量或者 struct/class 类型的同一个 field 的访问,都算作对同一个数据的访问。
  2. 对 struct/class 类型的不同 field 的访问,算作对不同数据的访问。

Happens-Before

  • 程序顺序规则:同一个线程中的每个操作 happens-before 于该线程中的任意后续操作。
var a: String

main(): Int64 {
    a = "hello, world"
    println(a)
    return 0
}

// OUTPUT:
// hello, world
  • 线程启动规则:如果线程 A 通过 spawn 创建线程 B,那么线程 A 的 spawn 操作 happens-before 于线程 B 中的任意操作。
var a: String = "123"

func foo(): Unit {
    println(a)
}

main(): Int64 {
    a = "hello, world"
    let fut: Future<Unit>= spawn {
        foo()
    }
    fut.get()
    return 0
}

// OUTPUT:
// hello, world
  • 线程终止规则:如果线程 A 调用 futureB.get() 并成功返回,那么线程 B 中的任意操作 happens-before 于线程 A 中的 futureB.get() 调用。如果线程 A 调用 futureB.cancel() 并且线程 B 在此之后访问 hasPendingCancellation,那么这两个调用构成 happens-before 关系。
var a: String = "123"

func foo(): Unit {
    a = "hello, world"
}

main(): Int64 {
    let fut: Future<Unit> = spawn {
        foo()
    }

    fut.get()
    println(a)
    return 0
}

// OUTPUT:
// hello, world
  • 线程同步规则:在同一个线程同步对象(例如互斥锁、信号量等)上的操作存在一个全序。一个线程对一个同步对象的操作(例如对互斥锁的解锁操作)happens-before 于这个全序上后续对这个同步对象的操作(例如对互斥锁的上锁操作)。

  • 原子变量规则:对于所有原子变量的操作存在一个 全序。一个线程对一个原子变量的操作 happens-before 于这个全序上后续所有的原子变量的操作。

  • 传递性规则:如果 A happens-before B 且 B happens-before C,那么 A happens-before C。