raku-type-atomicint
class atomicint is Int is repr('P6int') { }
atomicint은 CPU가 제공하는 원자 연산(atomic operation)을 수행할 수 있는 크기의 네이티브 정수예요. 32비트 CPU에서는 보통 32비트, 64비트 CPU에서는 보통 64비트 크기죠. 원자 연산을 쓰는 이식성 있는 코드를 작성하기 위해 존재하는 타입이에요.
참고: 2017.08 이전 Rakudo 릴리스에는 atomicint 지원이 없었어요.
# Would typically only work on a 64-bit machine and VM build.
my int64 $active = 0;
$active⚛++;
# Would typically only work on a 32-bit machine and VM build.
my int32 $active = 0;
$active⚛++;
# Will work portably, though can only portably assume range of 32 bits.
my atomicint $active = 0;
$active⚛++;
한 가지 짚고 넘어갈 점은, atomicint 타입을 쓴다고 자동으로 원자성이 보장되는 건 아니라는 거예요. 원자 연산과 함께 사용해야 해요.
# Correct (will always output 80000)
my atomicint $total = 0;
await start { for ^20000 { $total⚛++ } } xx 4;
say $total;
# *** WRONG *** due to lack of use of the atomicint type.
# Either works correctly or dies, depending on platform.
my int $total = 0;
await start { for ^20000 { $total⚛++ } } xx 4;
say $total;
# *** WRONG *** due to lack of use of the atomic increment operator.
my atomicint $total = 0;
await start { for ^20000 { $total++ } } xx 4;
say $total;
Routines
sub atomic-assign
multi atomic-assign(atomicint $ is rw, int $value)
multi atomic-assign(atomicint $ is rw, Int() $value)
네이티브 정수에 원자적 할당을 수행해요. 대상은 레시컬, 속성, 또는 네이티브 배열 원소 어디에 있어도 돼요. $value가 너무 커서 64비트 네이티브 정수로 언박싱할 수 없으면 예외가 던져져요. atomicint 크기가 32비트뿐이라면 범위를 벗어난 $value는 조용히 잘려요. atomic-assign은 필요한 배리어(barrier)를 모두 수행해서, 바뀐 값이 다른 스레드에 "공개"되도록 보장해요.
sub atomic-fetch
multi atomic-fetch(atomicint $ is rw)
네이티브 정수의 원자적 읽기를 수행해요. 대상은 레시컬, 속성, 또는 네이티브 배열 원소 어디에 있어도 돼요. 그냥 변수를 읽는 대신 이 루틴을 쓰면, 필요한 하드웨어 배리어를 수행하고 컴파일러가 읽기를 들어 올리는 것을 막아서, 다른 스레드의 최신 업데이트를 확실히 보게 돼요. 예를 들어
my atomicint $i = 0;
start { atomic-assign($i, 1) }
while atomic-fetch($i) == 0 { }
이 코드는 반드시 끝나는 게 보장돼요. 반면에
my atomicint $i = 0;
start { atomic-assign($i, 1) }
while $i == 0 { }
이렇게 쓰면, 컴파일러가 루프 안에서 $i가 갱신되지 않음을 관찰하고 읽기를 루프 밖으로 들어 올려서 프로그램이 끝나지 않을 수도 있어요. (그렇게 해도 법적으로는 문제없어요.)
sub atomic-fetch-inc
multi atomic-fetch-inc(atomicint $ is rw)
네이티브 정수에 원자적 증가를 수행해요. 하드웨어가 제공하는 원자 연산으로 처리돼요. 원자적이므로 락을 걸지 않아도 안전하게 쓸 수 있어요. 증가하기 전의 값을 돌려줘요. 오버플로는 조용히 감겨요(wrap).
sub atomic-fetch-dec
multi atomic-fetch-dec(atomicint $ is rw)
네이티브 정수에 원자적 감소를 수행해요. 하드웨어가 제공하는 원자 연산으로 처리되며, 락 없이 안전해요. 감소하기 전의 값을 돌려줘요. 오버플로는 조용히 감겨요.
sub atomic-fetch-add
multi atomic-fetch-add(atomicint $ is rw, int $value)
multi atomic-fetch-add(atomicint $ is rw, Int() $value)
네이티브 정수에 원자적 덧셈을 수행해요. 하드웨어 원자 연산으로 처리되고, 락 없이 안전해요. 덧셈을 수행하기 전의 값을 돌려줘요. 오버플로는 조용히 감겨요. $value가 너무 커서 64비트 정수로 언박싱할 수 없으면 예외가 나요. 아니라면 덧셈을 수행하기 전에 atomicint를 넘치는 값은 조용히 잘려요.
sub atomic-fetch-sub
multi atomic-fetch-sub(atomicint $ is rw, int $value)
multi atomic-fetch-sub(atomicint $ is rw, Int() $value)
네이티브 정수에 원자적 뺄셈을 수행해요. 하드웨어 원자 연산으로 처리되고, 락 없이 안전해요. 뺄셈을 수행하기 전의 값을 돌려줘요. 언더플로는 조용히 감겨요. $value가 너무 커서 64비트 정수로 언박싱할 수 없으면 예외가 나요. 아니라면 뺄셈을 수행하기 전에 atomicint를 넘치는 값은 조용히 잘려요.
sub atomic-inc-fetch
multi atomic-inc-fetch(atomicint $ is rw)
네이티브 정수에 원자적 증가를 수행해요. 하드웨어 원자 연산으로 처리되고, 락 없이 안전해요. 증가로 인한 결과 값을 돌려줘요. 오버플로는 조용히 감겨요.
sub atomic-dec-fetch
multi atomic-dec-fetch(atomicint $ is rw)
네이티브 정수에 원자적 감소를 수행해요. 하드웨어 원자 연산으로 처리되고, 락 없이 안전해요. 감소로 인한 결과 값을 돌려줘요. 오버플로는 조용히 감겨요.
sub cas
multi cas(atomicint $target is rw, int $expected, int $value)
multi cas(atomicint $target is rw, Int() $expected, Int() $value)
multi cas(atomicint $target is rw, &operation)
$target 위치의 네이티브 정수 값에 **원자적 비교 후 교환(compare and swap)**을 수행해요. 앞의 두 형식은 대략 이런 의미를 가져요.
my int $seen = $target;
if $seen == $expected {
$target = $value;
}
return $seen;
다만 이 작업이 실행되는 동안 $target에 대한 모든 메모리 접근이 막힌 것처럼, 단일 하드웨어 원자 명령으로 수행된다는 점이 달라요. 그래서 다른 동기화 없이 여러 스레드에서 이 연산을 시도해도 안전해요. 예를 들어
my atomicint $master = 0;
await start {
if cas($master, 0, 1) == 0 {
say "Master!"
}
} xx 4
이 코드는 신뢰성 있게 Master!를 딱 한 번만 출력해요. 0을 1로 바꾸는 데 성공하는 스레드는 오직 하나뿐이기 때문이죠.
$expected와 $value는 모두 필요하면 Int로 강제 변환되고 언박싱돼요. 값을 64비트 정수로 나타낼 수 없으면 예외가 나요. atomicint 크기가 32비트뿐이면 값들은 이 크기로 조용히 잘려요.
코드 객체를 받는 세 번째 형식을 볼게요. 먼저 현재 값을 원자적으로 fetch한 뒤 그 값으로 코드 객체를 호출해요. 그다음 코드 객체에 넘긴 값을 $expected로, 코드 객체의 결과를 $value로 하여 대상의 원자적 비교 후 교환을 시도해요. 실패하면 최신 값을 읽고, CAS가 성공할 때까지 다시 시도해요. 그래서 atomicint $i를 2로 곱하는 원자적 곱셈은 이렇게 구현할 수 있어요.
cas $i, -> int $current { $current * 2 }
$current * 2를 계산하는 동안 다른 스레드가 값을 바꿨다면, 블록이 최신 값으로 다시 호출되어 또 시도하고, 성공할 때까지 반복돼요.
Operators
infix ⚛=
multi infix:<⚛=>(atomicint $ is rw, int $value)
multi infix:<⚛=>(atomicint $ is rw, Int() $value)
네이티브 정수에 원자적 할당을 수행해요. 대상은 레시컬, 속성, 또는 네이티브 배열 원소 어디에 있어도 돼요. $value가 너무 커서 64비트 네이티브 정수로 언박싱할 수 없으면 예외가 나요. atomicint 크기가 32비트뿐이면 범위를 벗어난 $value는 조용히 잘려요. ⚛= 연산자는 필요한 배리어를 모두 수행해서, 바뀐 값이 다른 스레드에 "공개"되도록 보장해요.
prefix ⚛
multi prefix:<⚛>(atomicint $ is rw)
네이티브 정수의 원자적 읽기를 수행해요. 대상은 레시컬, 속성, 또는 네이티브 배열 원소 어디에 있어도 돼요. 그냥 변수를 읽는 대신 이 연산자를 쓰면, 필요한 하드웨어 배리어를 수행하고 컴파일러가 읽기를 들어 올리는 것을 막아서, 다른 스레드의 최신 업데이트를 확실히 보게 돼요. 예를 들어
my atomicint $i = 0;
start { $i ⚛= 1 }
while ⚛$i == 0 { }
이 코드는 반드시 끝나요. 반면에
my atomicint $i = 0;
start { $i ⚛= 1 }
while $i == 0 { }
이렇게 쓰면 컴파일러가 루프 안에서 $i가 갱신되지 않음을 관찰해 읽기를 루프 밖으로 들어 올려서, 프로그램이 끝나지 않을 수도 있어요.
prefix ++⚛
multi prefix:<++⚛>(atomicint $ is rw)
네이티브 정수에 원자적 증가를 수행해요. 하드웨어 원자 연산으로 처리되고, 락 없이 안전해요. 증가로 인한 결과 값을 돌려줘요. 오버플로는 조용히 감겨요.
postfix ⚛++
multi postfix:<⚛++>(atomicint $ is rw)
네이티브 정수에 원자적 증가를 수행해요. 하드웨어 원자 연산으로 처리되고, 락 없이 안전해요. 증가하기 전의 값을 돌려줘요. 오버플로는 조용히 감겨요.
prefix --⚛
multi prefix:<--⚛>(atomicint $ is rw)
네이티브 정수에 원자적 감소를 수행해요. 하드웨어 원자 연산으로 처리되고, 락 없이 안전해요. 감소로 인한 결과 값을 돌려줘요. 오버플로는 조용히 감겨요.
postfix ⚛--
multi postfix:<⚛-->(atomicint $ is rw)
네이티브 정수에 원자적 감소를 수행해요. 하드웨어 원자 연산으로 처리되고, 락 없이 안전해요. 감소하기 전의 값을 돌려줘요. 오버플로는 조용히 감겨요.
infix ⚛+=
multi infix:<⚛+=>(atomicint $ is rw, int $value)
multi infix:<⚛+=>(atomicint $ is rw, Int() $value)
네이티브 정수에 원자적 덧셈을 수행해요. 하드웨어 원자 연산으로 처리되고, 락 없이 안전해요. 덧셈의 결과로 평가돼요. 오버플로는 조용히 감겨요. $value가 너무 커서 64비트 정수로 언박싱할 수 없으면 예외가 나요. 아니라면 덧셈을 수행하기 전에 atomicint를 넘치는 값은 조용히 잘려요.
infix ⚛-=
multi infix:<⚛-=>(atomicint $ is rw, int $value)
multi infix:<⚛-=>(atomicint $ is rw, Int() $value)
네이티브 정수에 원자적 뺄셈을 수행해요. 하드웨어 원자 연산으로 처리되고, 락 없이 안전해요. 뺄셈의 결과로 평가돼요. 언더플로는 조용히 감겨요. $value가 너무 커서 64비트 정수로 언박싱할 수 없으면 예외가 나요. 아니라면 뺄셈을 수행하기 전에 atomicint를 넘치는 값은 조용히 잘려요.
infix ⚛−=
U+2212 마이너스 부호를 쓰는 ⚛-=의 동의어예요.