@cmpxchgStrong — 강한 원자적 비교-교환 연산

@cmpxchgStrong — 강한 원자적 비교-교환 연산

멀티스레드에서 여러 스레드가 같은 값을 동시에 바꾸려 할 때, "지금 값이 내가 기대하는 값이라면 새 값으로 바꿔라"라는 연산이 필요할 때가 있어요. 그걸 안전하게, 그러니까 중간에 다른 스레드가 끼어들지 못하게 하는 원자적 연산이 바로 @cmpxchgStrong이에요. 현재 값이 기대하는 값과 같으면 새 값으로 바꾸고 null을, 다르면 그대로 둔 채 현재 값을 돌려줘요.

출처: Zig Documentation

본문

@cmpxchgStrong강한(strong) 원자적 비교-교환(compare-and-exchange) 연산을 수행해요. 현재 값이 주어진 기대값(expected_value)과 같으면 연산은 null을 돌려주죠. 원자적 버전이라는 점만 빼면 아래 코드와 정확히 같은 동작이에요:

fn cmpxchgStrongButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_value: T) ?T {
    const old_value = ptr.*;
    if (old_value == expected_value) {
        ptr.* = new_value;
        return null;
    } else {
        return old_value;
    }
}

함수 시그니처는 이렇게 생겼어요:

@cmpxchgStrong(comptime T: type, ptr: *T, expected_value: T, new_value: T, success_order: AtomicOrder, fail_order: AtomicOrder) ?T

cmpxchg를 재시도 루프(retry loop) 안에서 쓴다면 @cmpxchgWeak이 더 나은 선택이에요. 기계 명령어로 더 효율적으로 구현될 수 있거든요. 강한 버전은 "성공이 보장될 때까지 반복"이라는 의미를 담고 있어 한 번만 시도하는 흐름에 어울려요.

몇 가지 타입 제약이 있어요:

  • T는 포인터, bool, 정수, enum, 또는 packed struct여야 해요.
  • @typeInfo(@TypeOf(ptr)).pointer.alignment@sizeOf(T)보다 크거나 같아야 해요.
  • AtomicOrder@import("std").lang.AtomicOrder에서 찾을 수 있어요.

함께 보면 좋은 연산들도 있어요:

  • @atomicStore
  • @atomicLoad
  • @atomicRmw
  • @cmpxchgWeak

더 알아보기 (Learn more)

@cmpxchgStrong은 원자적 메모리 연산 중 하나일 뿐이에요. 재시도 루프에는 더 효율적인 @cmpxchgWeak, 읽기·쓰기·읽고-수정-쓰기에는 @atomicLoad·@atomicStore·@atomicRmw를 함께 익혀 두면 동시성 코드를 짤 때 훨씬 수월해져요.