跳到主要内容
版本:master

readonly, parallel 和 readwrite

readonly, parallel 和 readwrite 在修饰/描述不同的东西时,含义有所不同

引用容器类型(map, array 和 buffer)实例的运行时属性

对于 引用容器类型,因为它们本身被用于储存动态数量的元素,具有内部可变性。对于这些类型的实例而言,parallel, readonly 是它们的运行时属性,用于描述它们的内部是否可变。

既不是 readonly,也不是 parallel 的实例被认为是 readwrite 的。

状态说明
readonlyRO 的数据完全不具有内部可变性
parallelPA 的数据具有受限的内部可变性,在容器的元素数量不发生改变的情况下,可以修改容器中特定位置的元素
readwriteRW 的数据具有完全的内部可变性,可以修改容器的元素数量和容器中特定位置的元素

当一个容器实例是 Readonly 或 Parallel 时,其内部可变性受限:

  • 对于 Readonly 的容器,其内部状态完全不可变,即实例是只读的;不涉及到并行访问的数据争用问题。
  • 对于 Parallel 的容器,尽管里面的元素仍然可以被修改,但是容器本身的结构不会发生改变,容器本身不会因为数据争用损坏。

因此,对于 Readonly 和 Parallel 的容器实例,允许它们从一个域直接传递(即不发生复制)到另一个域。

Readwrite 的容器,为了确保安全,不允许跨域传递(即必须发生复制)。

parallel方法

此处的方法指的是在脚本中直接定义的具名或匿名函数。方法可以被 parallel 修饰(不能被 readonly 修饰)。

通常情况下,普通的方法可以访问对象在当前程序中定义的成员变量,为了保证在这个操作期间的内存安全,当一个普通的方法被调用时,协程 应当进入方法所属的对象域。

但是对于一些方法来说,它们本身可能是无状态的,或者通过其他手段保证并行安全;此时可以使用 parallel 修饰方法。被标注为 parallel 的方法不能直接访问对象的 readwrite 成员变量,亦不能直接调用对象自己的 非parallel 方法。


int member = 15;
parallel int parallel_member := 20;

void foo()
{

}
parallel void bar()
{

}
parallel void baz()
{
bar(); // Ok: 可以调用 parallel 方法
// foo(); // error: 不能调用非 parallel 方法
this=>foo(); // Ok: 明确地跨域调用仍然是被允许的
this.foo(); // Ok: 前提是能够确保此时的当前域和 this 的对象域一致

// writeln(member); // error: 不能访问 readwrite 成员变量
writeln(parallel_member); // Ok: 可以访问 parallel 成员变量
}

一个方法被 parallel 修饰时,其内在的含义是(下满足其一):

  • 这个方法是无副作用的,即调用这个方法不涉及除了参数以外的外部状态修改
  • 这个方法对外部状态的修改是受限且安全的,例如通过原子操作进行计数
  • 这个方法通过其他手段保证并行安全(例如 try_lock)

修饰为 parallel 的方法在被调用时,协程不会进入方法所属的对象域,而是保持在之前的域上;因此这个方法 可以被不同协程并行地调用。

被修饰为 parallel 的方法不能调用当前对象的非 parallel 方法;

如果使用跨域方式调用 parallel 方法,跨域不会发生;但是参数复制仍然会执行。

对象(脚本)的 parallel 标注

源码中可以用 #pragma parallel 来修饰一个程序。如果这么做,程序中的所有方法都相当于被 标注为 parallel;同时程序中的所有成员变量都应当是 readonly 或 parallel 的。

parallel 的程序不允许组合其他 非 parallel 的组件,但反过来是被允许的。

被标注为 parallel 的程序,其实例化(通过 load_staticnew_object)产生的对象也是 parallel 对象(这一属性大多数情况下并没有特别的意义)。

成员变量的 parallel 或 readonly 标注

成员变量可以被 readonlyparallel 修饰。不过需要做出区分:此时修饰的并不是成员变量本身,而是限制成员变量只能被赋值为 readonly 或 parallel 的值。

也就是说,即便被标注为 readonly,成员变量依然可以被重新赋值,但赋值操作的右值也应当是 readonly 的。

如果一个成员变量既没有被标注为 readonly,也没有被标注为 parallel,那么它被认为是 readwrite 的。