[CS/운영체제] 멀티스레드와 동시성 - 24부

황제연·2025년 7월 23일
0

CS학습

목록 보기
144/193
post-thumbnail

원자적 연산으로 구현

앞선 예제에서 만든 방법은 연산이 원자적으로 이루어지지 않았다는 문제가 존재했습니다
1. 락 사용 여부를 확인합니다
2. 락의 값을 변경합니다

이러한 문제를 락 없이 해결하기 위해서는 두 연산을 하나의 원자적인 연산으로 만들어주어야합니다
lock.compareAndGet(false, true)
이렇게 한다면 락의 사용여부를 확인하며 값을 변경할 수 있습니다

변경 코드

public void lock(){
	while(!lock.compareAndSet(false, true)){
		// 락 획득할 때까지 스핀대기(바쁜 대기)를 합니다
	}
}

이제 두개 이상의 스레드가 동시에 접근해도, 스레드 하나씩 락을 획득하고, 반납합니다!
앞선 두개의 연산이 이제 하나의 원자적인 연산으로 바뀌었습니다
1. 락을 사용하지 않는다면 락의 값을 변경합니다

원자적 연산이란

우너자적 연산이란 스레드가 나눌 수 없는 하나의 연산입니다
따라서 여러 스레드가 동시에 실행해도 안전합니다
CAS를 사용하면 원자적인 연산을 무거운 동기화 작업없이 가벼운 락으로 만들 수 있습니다

동기화 락을 사용하는 경우 스레드가 락을 획득하지 못하면, BLOCKED, WAITING등으로 상태가 변합니다
또한 대기 상태의 스레드를 깨워야하는 무겁고 복잡한 과정이 추가로 들어가며
성능이 상대적으로 느려집니다

반면에 CAS를 활용한 락 방식은 락이 없고, while문을 반복할 뿐입니다
따라서 대기하는 스레드도 RUNNABLE 상태를 유지하면서 가볍고 빠르게 동작할 수 있습니다

CAS 단점

하지만 반복문과 CAS를 사용해서 락을 대체하는 방식도 단점이 있습니다
만약 오래걸리는 로직이 포함된다면 스레드가 BLOCKED, WAITING 상태로 빠지지 않더라고
RUNNABLE 상태로 락을 획득할 때까지 while문을 반복하는 문제가 있습니다

따라서 락을 기다리는 스레드가 CPU를 계속 사용하면서 대기합니다
BLOCKED, WAITING 상태의 스레드는 CPU를 거의 사용하지 않지만, RUNNABLE 상태로
while문을 반복 실행하는 방식을 결국 CPU 자원을 계속 사용하는 것입니다

동기화 락을 사용하면..

동기화 락을 사용하면 RUNNABLE 상태의 스레드가 BLOCKED, WAITING 상태에서
다시 RUNNABLE 상태로 이동합니다

이 사이에 CPU 자원을 거의 사용하지 않을 수 있습니다
그래서 동기화 락을 사용하는 방식보다 스레드를 RUNNABLE로 살려둔 상태에거 계속 락 획득을
반복 체크하는 것이 더 효율적일 때만 사용해야합니다
이 방식은 스레드의 상태가 변경되지 않기 때문에 매우 빠르게 락을 획득하고 또 바로 실행할 수 있는 장점이 있습니다

CAS를 사용하는 경우

안전한 임계 영역이 필요하지만 매우 짧게 끝날 때 사용해야합니다
특히 CPU 사이클이 금방 끝나는 연산에 사용하면 효과적입니다.
만약 DB 결과를 대기하거나 외부 API 요청을 기다리거나 한다면 CPU를 계속 사용하며 기다리는
최악의 결과가 나올 수 있습니다

스핀 락

스레드가 락이 해제되기를 기다리면서 반복문을 통해 계속 확인하는 모습이
마치 제자리에서 회전하는 것처럼 보이기 떄문에 스핀 락(Spin Lock)이라고 합니다

이런 방식에서 스레드가 락을 획득할 때까지 대기하는 것을 스핀 대기(spin-wait) 또는
CPU 자원을 계속 사용하면서 바쁘게 대기한다고 해서 바쁜 대기 (busy-wait)이라고 합니다

이런 스핀 락 방식은 아주 짧은 CPU연산을 수행할 때 사용해야 효율적이고,
잘못 사용하면 CPU 자원을 더 많이 사용할 수 있습니다

즉, 스핀 락은 락을 획득하기 위해 자원을 소모하면서 확인하는 락 매커니즘을 의마하고
이런 스핀락은 CAS를 통해 구현할 수 있습니다

참고

  • 김영한의 실전 자바 - 고급 1편
profile
Software Developer

0개의 댓글