并发
仓颉线程
仓颉编程语言提供抢占式的并发模型,其中仓颉线程是基本的执行单元。仓颉线程由仓颉运行时自行管理并非是底层 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.
}
同步机制
原子操作
原子操作确保指令被原子地执行,即执行过程中不会被中断。对原子变量的一个写操作,对于后续对同一个原子变量的读操作来说,总是可见的。此外,原子操作是非阻塞的动作,不会导致线程阻塞。仓颉语言提供了针对整数类型(包括 Int8
、Int16
、Int32
、Int64
、UInt8
、UInt16
、UInt32
、UInt64
),布尔类型(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
}
注意:
- 开发者在实现该接口时需要保证底层互斥锁确实支持嵌套锁。
- 开发者在实现该接口时需要保证在出现锁状态异常时抛出
IllegalSynchronizationStateException
。
ReentrantMutex
ReentrantMutex
提供如下方法:
lock(): Unit
:获取锁,如果获取不到,阻塞当前线程tryLock(): Bool
:尝试获取锁unlock(): Unit
:释放锁
ReentrantMutex
是内置的锁,在同一时刻最多只能被一个线程持有。如果给定的 ReentrantMutex
已经被另外一个线程持有,lock
方法会阻塞当前线程直到该互斥锁被释放,而 tryLock
方法会立即返回 false
。
ReentrantMutex
是可重入锁,即如果一个线程尝试去获取一个它已经持有的 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
修饰的代码块内有break
或continue
表达式,并且该表达式的执行会导致程序跳出该代码块,那么会自动调用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
对象的 wait
、notify
或 notifyAll
方法前,需要确保当前线程已经持有对应的 Monitor
锁。wait
方法包含如下动作:
- 添加当前线程到该
Monitor
对应的等待队列中 - 阻塞当前线程,同时完全释放该
Monitor
锁,并记录获取次数 - 等待某个其它线程使用同一个
Monitor
实例的notify
或notifyAll
方法向该线程发出信号 - 唤醒当前线程并同时获取
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 节定义),那么形成一个数据竞争。
对“同一个数据访问”的定义:
- 对同一个 primitive type、enum、array 类型的变量或者 struct/class 类型的同一个 field 的访问,都算作对同一个数据的访问。
- 对 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。