守卫和锁
Nim 提供了诸如锁、原子性内部函数或条件变量这样的常见底层并发机制。
Nim 通过附带编译指示,显著地提高了这些功能的安全性:
- 引入 guard 注解,以防止数据竞争。
- 每次访问受保护的内存位置,都需要在适当的 locks 语句中进行。
守卫和锁块
受保护的全局变量
对象字段和全局变量都可以使用 guard 编译指令进行标注:
import std/locks
var glock: Lock
var gdata {.guard: glock.}: int
然后,编译器会确保每次访问 gdata 都在 locks 块中:
proc invalid =
# invalid: unguarded access:
echo gdata
proc valid =
# valid access:
{.locks: [glock].}:
echo gdata
为了能够方便地初始化,始终允许在顶层访问 gdata。 假定 (但不强制)所有顶层语句都在发生并发操作之前执行。
我们故意让 locks 块看起来很丑,因为它没有运行时的语意,也不应该被直接使用! 它应该只在模板里出现,再由模板实现运行时的加锁操作:
template lock(a: Lock; body: untyped) =
pthread_mutex_lock(a)
{.locks: [a].}:
try:
body
finally:
pthread_mutex_unlock(a)
守卫不需要属于某种特定类型。它足够灵活,可以对低级无锁机制建模:
var dummyLock {.compileTime.}: int
var atomicCounter {.guard: dummyLock.}: int
template atomicRead(x): untyped =
{.locks: [dummyLock].}:
memoryReadBarrier()
x
echo atomicRead(atomicCounter)
locks 指示接受一个锁表达式列表 locks: [a, b, …] ,以支持 多锁 语句。
保护常规地址
guard 注解也可以用于保护对象中的字段。这时,需要用同一个对象的另一个字段或者一个全局变量作为守卫。
由于对象可以驻留在堆上或栈上,这就大大地增强了语言的表达能力:
import std/locks
type
ProtectedCounter = object
v {.guard: L.}: int
L: Lock
proc incCounters(counters: var openArray[ProtectedCounter]) =
for i in 0..counters.high:
lock counters[i].L:
inc counters[i].v
有 x.L 的守卫就可以访问字段 x.v。模板展开后得到:
proc incCounters(counters: var openArray[ProtectedCounter]) =
for i in 0..counters.high:
pthread_mutex_lock(counters[i].L)
{.locks: [counters[i].L].}:
try:
inc counters[i].v
finally:
pthread_mutex_unlock(counters[i].L)
编译器会分析检查 counters[i].L 是否是用于受保护位置 counters[i].v 的那个锁。 因为这个分析能够处理像 obj.field[i].fieldB[j] 这样的路径,所以我们叫它 path analysis “路径分析”。
路径分析 目前不健全 ,但也不是一点儿用都没有。如果两条路径在语法上相同,则认为是等价的。
这意味着下面的代码(目前)可以编译通过,虽然真不应该如此:
{.locks: [a[i].L].}:
inc i
access a[i].v
当前内容版权归 vectorworkshopbaoerjie 或其关联方所有,如需对内容或内容相关联开源项目进行关注与资助,请访问 vectorworkshopbaoerjie .