readonly, parallel 和 readwrite
readonly, parallel 和 readwrite 在修饰/描述不同的东西时,含义有所不同
引用容器类型(map, array 和 buffer)实例的运行时属性
对于 引用容器类型,因为它们本身被用于储存动态数量的元素,具有内部可变性。对于这些类型的实例而言,parallel, readonly 是它们的运行时属性,用于描述它们的内部是否可变。
既不是 readonly,也不是 parallel 的实例被认为是 readwrite 的。
| 状态 | 说明 |
|---|---|
| readonly | RO 的数据完全不具有内部可变性 |
| parallel | PA 的数据具有受限的内部可变性,在容器的元素数量不发生改变的情况下,可以修改容器中特定位置的元素 |
| readwrite | RW 的数据具有完全的内部可变性,可以修 改容器的元素数量和容器中特定位置的元素 |
当一个容器实例是 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_static 和 new_object)产生的对象也是 parallel 对象(这一属性大多数情况下并没有特别的意义)。
成员变量的 parallel 或 readonly 标注
成员变量可以被 readonly 或 parallel 修饰。不过需要做出区分:此时修饰的并不是成员变量本身,而是限制成员变量只能被赋值为 readonly 或 parallel 的值。
也就是说,即便被标注为 readonly,成员变量依然可以被重新赋值,但赋值操作的右值也应当是 readonly 的。
如果一个成员变量既没有被标注为 readonly,也没有被标注为 parallel,那么它被认为是 readwrite 的。