아토믹이란 원자적 연산 타입이다. 다음과 같은 특징이 있다.
보면 알다시피 lock-free이기 때문에 연산 속도가 기존 Mutex나 세마포어 등등 구현보다 훨씬 빠르다. 이유는 코드로 구현하는 구현체가 아니라 아토믹 연산이 CPU 자체 연산 중 하나이기 때문이다. 그래서 다른 쓰레드에서 변경되는 중간 과정을 확인하지 못한다.
아토믹은 간단한 정수형들같은 기본 타입만 제공한다.
앞에 설명했던 lock-free인 것을 반대로 말해보면 아토믹은 연산 순서를 정할 수 없다. 그래서 중간에 아토믹의 연산 순서를 보장해주는 Memory Ordering이 들어간다.
종류는 다음과 같다.
Relaxed: 가장 낮은 순위, 연산 간 순서를 보장해주지 않고 단순히 thread-safe만 보장한다.
Release: 해당 메모리 오더링을 가진 연산이 발생한 이후에 다른 연산들이 발생하는 것을 보장한다. 즉, Release
연산이 완료된 후에 모든 후속 연산이 관찰될 수 있다는 것을 의미한다.
Acquire: 해당 메모리 오더링을 가진 연산이 발생하기 이전에 다른 연산들이 발생했음을 보장한다. 즉, Acquire
연산이 시작되기 전에 모든 이전 연산이 완료되었음을 보장한다.
AcqRel: Release
연산과 Acquire
연산을 합친 것으로, 이전의 모든 연산이 발생한 후에 이 연산이 시작되고, 이후의 모든 연산이 이 연산이 완료된 후에 발생함을 보장한다.
SeqCst: Sequentially Consistent의 약자로, 가장 강력한 메모리 순서 보장을 제공한다. 모든 스레드에서 모든 메모리 연산의 순서를 동일하게 유지하도록 보장한다.
위 종류만 보고는 한번에 이해하기가 쉽지 않다.
보통 Release
는 쓰기 연산에서, Acquire
는 읽기 연산에서 일어나고 AcqRel
은 복잡한 연산, CAS(Compare And Swap), 읽고 쓰는 연산 등에서 일어난다.
Compare And Swap이란 특정 조건 비교 후 값을 바꾸는 연산을 말한다. 값을 비교하려면 읽고, 값을 바꾸려면 써야하니까 AcqRel
이 적절한 메모리 오더링인 것이다.
SeqCst
메모리 오더링은 순차적 실행인 것처럼 여러 쓰레드에서 연산 순서를 보장해준다.
lock-free라고 lock을 안 쓰고 오직 아토믹으로만 연산을 처리하려고 한다면 오히려 오버헤드가 발생할 수 있다.
잦은 동일 아토믹 변수 접근
아토믹은 분명 CPU 연산이기 때문에 빠르다. 하지만 잦게 접근하다 보면 캐시 라인 동기화가 일어나서 오버헤드가 커질 것이다. 이런 잦은 접근이 필요할 때에는 아토믹보단 lock이 더 효율적이다.
복잡한 연산
복잡한 연산을 아토믹 연산으로 처리하려고 하면, 여러 개의 아토믹 연산을 조합해야 하므로 코드가 복잡해지고, 성능도 저하될 수 있다. 복잡한 연산을 수행할 때는 락을 사용하여 임계 구역을 설정하고, 한번에 연산을 처리하는 것이 더 효율적일 수 있다.
참조
전문적이시네요