가드와 락
가드와 락 (Guards and locks)
Nim은 락(lock), 원자적 내장 함수(atomic intrinsic), 조건 변수(condition variable) 같은 흔한 저수준 동시성 메커니즘을 제공해요.
Nim은 추가 프라그마를 통해 이런 기능들의 안전성을 크게 높여줘요:
- 데이터 레이스(data race)를 막기 위해
가드(guard):idx: 어노테이션을 도입했어요. - 가드된 메모리 위치에 접근할 때마다 반드시 적절한
locks:idx: 문 안에서 이뤄져야 해요.
가드와 락 섹션 (Guards and locks sections)
전역 변수 보호하기 (Protecting global variables)
객체 필드와 전역 변수는 guard 프라그마로 어노테이션할 수 있어요:
import std/locks
var glock: Lock
var gdata {.guard: glock.}: int
그러면 컴파일러는 gdata에 대한 모든 접근이 locks 섹션 안에서 일어나도록 강제해요:
proc invalid =
# 유효하지 않음: 가드되지 않은 접근:
echo gdata
proc valid =
# 유효한 접근:
{.locks: [glock].}:
echo gdata
gdata에 대한 최상위(top level) 접근은 항상 허용돼서 편리하게 초기화할 수 있어요. 모든 최상위 문장은 어떤 동시 작업이 일어나기 전에 실행된다고 가정하지만(그리고 강제하지는 않아요) 그래요.
locks 섹션은 일부러 보기 흉하게 만들었어요. 런타임 의미(runtime semantics)가 없어서 직접 쓰면 안 되거든요! 런타임에 어떤 형태의 락킹을 함께 구현하는 템플릿 안에서만 쓰도록 하세요:
template lock(a: Lock; body: untyped) =
pthread_mutex_lock(a)
{.locks: [a].}:
try:
body
finally:
pthread_mutex_unlock(a)
가드는 특정 타입일 필요가 없어요. 저수준 락프리(lockfree) 메커니즘을 모델링할 만큼 유연해요:
var dummyLock {.compileTime.}: int
var atomicCounter {.guard: dummyLock.}: int
template atomicRead(x): untyped =
{.locks: [dummyLock].}:
memoryReadBarrier()
x
echo atomicRead(atomicCounter)
locks 프라그마는 멀티 락(multi lock) 문을 지원하기 위해 락 표현식의 목록 locks: [a, b, ...]을 받아요.
일반적인 위치 보호하기 (Protecting general locations)
guard 어노테이션은 객체 안의 필드를 보호할 때도 쓸 수 있어요. 이때 가드는 같은 객체 안의 다른 필드이거나 전역 변수여야 해요.
객체는 힙(heap)이나 스택(stack)에 존재할 수 있으므로, 이 기능은 언어의 표현력을 크게 높여줘요:
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.v에 대한 접근은 그 가드인 x.L이 활성화되어 있으므로 허용돼요. 템플릿 확장 후에는 이렇게 됩니다:
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)를 다루기 때문에 경로 분석(path analysis):idx: 이라고 불러요.
경로 분석은 현재는 부정확(unsound)하지만, 그렇다고 쓸모없어지지는 않아요. 두 경로는 구문적으로 동일하면 동등한 것으로 간주돼요.
따라서 다음 코드는 정말 그래서는 안 되지만 (지금은) 컴파일돼요:
{.locks: [a[i].L].}:
inc i
access a[i].v