원자적 연산이란?
더 이상 작은 단위로 쪼갤 수 없는, 한 번에 완전히 수행되는 연산
다른 어떤 연산의 방해도 받지 않고 통째로 완료되거나 아예 시작되지 않거나 둘 중 하나만 보장되는 연산
중요한 이유
동시성이 존재하는 환경에서 공유 자원의 일관성을 보장할 수 있기 때문입니다. 여러 스레드가 동시에 같은 데이터에 접근할 때, 원자성이 보장되지 않으면 데이터가 손상될 수 있습니다.
원자성이 필요한 이유
i라는 변수의 값을 1 증가시키는 연산인 i++를 생각해보겠습니다.
i++ 연산은 코드 상으로는 한 줄로 보이지만, 실제로는 CPU에서 다음 세 가지 비원자적(Non-atomic) 단계로 이뤄집니다 :
1.
메모리에서 i의 현재 값을 레지스터로 불러옵니다(LOAD)
2.
레지스터 값을 1 더합니다(INCREMENT)
3.
증가된 값을 다시 메모리의 i에 씁니다(STORE)
예시1) 두 개의 스레드가 동시에 i++ 연산을 실행한다면?
다음과 같이, 연산이 뒤섞이는 레이스 컨디션(Race Condition)이 발생할 수 있습니다 :
스레드 1 | 스레드 2 | 메모리(i) |
i 를 레지스터로 로드(값 : 5) | 5 | |
i 를 레지스터로 로드(값 : 5) | 5 | |
레지스터 값에 1을 더함(값 : 6) | 5 | |
레지스터 값에 1을 더함(값 : 6) | 5 | |
레지스터 값(6)을 i에 저장 | 6 | |
레지스터 값(6)을 i에 저장 | 6 | |
결과 | 6(7이 아님) |
예시2) 은행에서 한 계좌의 돈을 빼서 다른 계좌로 옮기는 과정 중, 만약 이 과정이 중간에 멈춘다면?
•
돈이 복사될 수도
•
돈이 공중에 떠서 사라질 수도
원자적 연산 구현 방법
하드웨어 지원
하드웨어적인 지원은 이런 것도 있구나 하시면 될 것 같습니다.
•
현대 CPU 아키텍처들은 xchg, cmpxchg 같은 명령어들이 단일 클럭 사이클 내에 완료되도록 보장합니다
•
LOCK이라는 특별한 명령어 접두사를 사용하면 그 뒤에 오는 연산을 원자적으로 만들 수 있습니다. LOCK이 붙으면 CPU는 해당 연산이 완료될 때까지 다른 CPU나 코어가 메모리에 접근하지 못하도록 버스를 잠급니다.
소프트웨어적 구현
뮤텍스 락
임계 구역(Critical Section) : 여러 스레드가 동시에 접근해서는 안 되는 코드 영역
락을 사용해 한 번에 하나의 스레드만 임계 구역에 진입할 수 있도록 합니다. lock()과 unlock() 연산 자체는 원자적으로 구현해야 합니다.

