CAS (Compare and Set)

CAS - 동기화와 원자적 연산

1. CAS (Compare and Set)

락을 사용하지 않고 원자적인 연산을 수행할 수 있는 방법. Lock-free 기법이라고도 함. CAS는 내부 연산 속도가 빠를 수록 락에 비해 성능이 우수하지만, 내부 연산 속도가 느리면 락에 비해 성능이 더 느려질 수 있음

package thread.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class CasMainV1 {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(0);
        System.out.println("start value = " + atomicInteger.get());

        boolean result1 = atomicInteger.compareAndSet(0, 1);
        System.out.println("result1 = " + result1 + ", value = " + atomicInteger.get());

        boolean result2 = atomicInteger.compareAndSet(0, 1);
        System.out.println("result2 = " + result2 + ", value = " + atomicInteger.get());
    }
}
  • CompareAndSet을 사용하는 경우, 내부적으로 원자적으로 계산하게 됨

    • 원래는 조회하고 값을 갱신하는 작업은 원자적이지 않지만, CompareAndSet은 이를 원자적으로 수행. 이게 가능한 이유는 하드웨어에서 제공하는 기능 덕분

    • CompareAndSet(0, 1) -> value 값이 0 이면 1로 변경하는 것. 그렇기 때문에 1번째 연산은 성공, 2번째 연산은 실패를 리턴.

  • 둘다 성공할 수 있는 이유는, atomicInteger.get()을 사용하여 값을 읽고, getValue+1을 사용하여 값을 메모리에 갱신. 만약 실패하면 성공할때까지 계속 시도.

1.1. 멀티스레드 상황

  • 정상적으로 2로 증가한 것을 확인

  • AtomicInteger에서 제공하는 incrementAndGet() 코드는 직접 작성한 incrementAndGet()코드와 똑같이 CAS를 활용하도록 작성되어있음.

  • CAS는 락 충돌이 자주 발생하지 않는 (낙관적 락) 상황에서 락을 획득, 반납하고 WAITING, RUNNABLE이 되는 시간이 없어서 오버헤드가 줄어듬.

2. Lock vs CAS

2.1. Lock

  • 비관적 접근

  • 데이터가 접근하기 전에 항상 락을 획득

  • 다른 스레드의 접근을 막음

2.2. CAS

  • 낙관적 접근

  • 락을 사용하지 않고 데이터에 바로 접근

  • 충돌이 발생하면 그때 재시도

3. CAS 구현

3.1. 락 구현1 (bad)

  • 위 상황에서는 당연히 원자적 연산이 아니기 떄문에 문제가 발생.

    • 락 사용 여부 확인

    • 락의 값 변경

만약 이 두 코드를 하나로 묶어서 원자적으로 처리 한다면?

CAS 연산을 사용하면 두 연산을 하나로 묶어서 처리 가능. 락의 사용 여부를 확인하고, 그 값이 기대하는 값과 같다면 변경. CAS 연산이 필요한 시기이다.

3.2. 락 구현2 (good)

  • CAS 연산을 사용하는 AtomicBoolean을 사용했을때, 락 사용 여부확인과 락의 값 변경이 원자적으로 됨

  • 결과를 보면 락이 잘 적용된 것을 확인.

4. CAS 단점

  • 스핀락으로 성능 저하 발생.

    • 데이터의 캐시라인 독점(MESI프로토콜의 Modified)이 해제될때까지 bus snooping을 하는 스핀으로 인한 성능 저하.

    • Modified가 해제되고, 스핀중이던 모든 스레드들이 동시에 연산을 하면서 발생하는 성능 저하. 하나를 제외한 나머지 연산들은 의미가 없는 연산임.

자세한건 원자적으로 동작하는 방식을 참고: https://wonjoon.gitbook.io/joons-til/java/mesi-protocol-in-cas

Last updated