Post

동기화(Synchronization)

동기화

동기화란 무엇이고, 어떤 상황에서 필요한 것일까?

만약 우리가 상한 귤의 갯수를 새는 프로그램을 실행시킨다고 가정해보자.

만약 그 프로그램이 싱글코어 CPU 환경에서 실행되고, 귤을 담은 상자의 갯수가 2상자라 2개의 스레드로 작동시킬려고 한다.

1번 상자의 상한 귤이 2개, 2번 상자에는 5개가 있다고 친다면, 우리는 프로그램이 끝났을 때 상한 귤의 갯수는 7개가 될 것이라고 기대를 할 것이다.

하지만 7개가 정상적으로 나올 수도 있지만, 여러번 실행시 항상 7개로 나오지 않는 문제점이 발생할 것이다. 왜 이런 일이 발생할까?

아래 예제를 통해 살펴보자.

1
2
3
4
5
public class Counter {
    private int state = 0;
    public void increment() { state++; }
    public int get() { return state; }
}

위 코드는 상한 귤일 때 increment가 실행되며 상한 귤의 갯수를 하나씩 증가시켜주는 클래스이다.

이 클래스는 CPU에서는 실제로 어떻게 실행될까? 아래와 같이 변환된다.

1
2
3
LOAD state to R1 // 메모리에 있는 state라는 값을 CPU에 있는 Register에 넣는다.
R1 = R1 + 1 // 레지스터의 값을 + 1 하고 다시 레지스터에 넣는다.
STORE R1 to state // 바뀐 값을 메모리에 있는 state에 넣는다.

이게 우리 프로그램의 문제와 어떤 상관이 있는걸까?

우리는 위에서 싱글코어에 2개의 스레드를 사용한다고 했다.

T1(Thread 1)과 T2(Thread2)가 있을 때 정상적으로 작동할 경우는 아래와 같다.

1
T1 실행 -> LOAD -> R1++ -> STORE -> C.S(컨텍스트 스위칭) -> T2 실행 -> LOAD -> R1++ -> STORE

하지만 다음과 같이 작동할 때 문제가 일어난다.

1
T1 실행 -> LOAD -> R1++ -> C.S(컨텍스트 스위칭) -> T2 실행 -> LOAD -> R1++ -> STORE -> STORE 

위와 같이 C.S가 T1이 STORE 하기 전에 일어난다면 메모리에 증가시킨 R1 값을 넣지 못했기 때문에 T2가 LOAD 했을 때 메모리상 state = 0인 상태이므로 state 값은 2가 아닌 1이 나오게 된다.

이런 상황에 관련된 개념은 다음과 같다.

경쟁 조건 (race condition)

  • 여러 프로세스/스레드가 동시에 같은 데이터를 조작할 때 타이밍이나 접근 순서에 따라 결과가 달라질 수 있는 상황

동기화(synchronization)

  • 여러 프로세스/스레드를 동시에 실행해도 공유 데이터의 일관성을 유지하는 것

그럼 어떻게 동기화해 문제를 해결할 것인가?

단편적으로 생각한다면 C.S를 하지 않도록 막아야겠다고 생각할 수 있다.

하지만 싱글코어가 아닌 멀티코어에서는 먹히지 않는 방법이다. 멀티코어의 각 스레드가 LOAD를 동시에 접근한다고 가정할 때 C.S는 일어나지 않고 경쟁 조건이 되기 때문이다.

이 뿐만 아니라 다른 여러 문제 때문에 C.S를 막아서 해결하는건 바람직하지 못하다.

그래서 해결책으로 등장한 것이 임계 영역이다.

임계 영역(critical section)

  • 공유 데이터의 일관성을 보장하기 위해 하나의 프로세스/스레드만 진입해서 실행 가능한 영역

C.S를 막는게 아니라 하나의 프로세스나 스레드만 진입하게 설정한다면 싱글코어든 멀티코어든 상관없이 문제를 해결할 수 있다.

이렇듯 critical section과 관련된 문제들을 critical section problem이라 불린다.

critical section problem

critical section problem을 해결하기 위한 뼈대는 다음과 같다.

1
2
3
4
5
6
do {
    entry section // critical section에 진입할 요건이 되는지 확인
        critical section // 진입해야할 영역
    exit section // critical section에서 빠저나올 때 이후에도 이 솔루션이 잘 작동하도록 필요한 조치를 취함
        remainder section // 그냥 실행
} while (TRUE)

critical section problem의 해결책이 되기 위한 조건

  1. mutual exclusion (상호 배제)

    • 한번에 하나의 프로세스나 스레드만이 critical section 에서 실행 가능
  2. progress (진행)
    • 만약 critical section이 비어있고 프로세스나 스레드들이 critical section에서 실행되기 원하다면 그 중 하나는 실행되어야 함
  3. bounded waiting (한정된 대기)
    • 어떤 프로세스나 스레드가 무한정 critical section에 들어가지 못하고 대기해서는 안된다.

위 조건을 모두 만족해야한다.

정리

이렇듯 우리는 critical section problem이 발생하지 않도록 프로그램을 짜야한다.

자바에서 기본적으로 제공하는 클래스들도 critical section problem이 발생할 수 있다.

만약 멀티스레드 환경에서 프로그램을 짜야한다면 클래스를 사용 전 API 문서를 확인해 Thread-safe한지 확인하고, 코딩시에는 동기화해 Thread-safe한 코드를 작성하자.

race condition이 발생하지 않도록 말이다….

출처)

동기화(synchronization), 경쟁 조건(race condition), 임계 영역(critical section)을 자세하게 설명합니다! 헷갈리시는 분들 꼭 보세요! (youtube.com)

This post is licensed under CC BY 4.0 by the author.