https://www.youtube.com/watch?v=vp0Gckz3z64&list=PLcXyemr8ZeoQOtSUjwaer0VMJSMfa-9G-&index=4

 

영상에서 얻고자하는것

왜 동기화(Synchronization)이 중요한지 깊이 알게된다.

 

동기화 없으면 생길 수 있는 일

하나의 객체를 두개의 스레드가 접근할때 생긴 일에 대해 알아보자.

왜 동기화가 중요한지 알게된다.

 

counter객체를 활용해보자.

스레드 두개를 써서 상한 귤을 골라내보자. 

for(귤 in 귤박스){
	if(귤 상태 is 불량){
    	badCounter.increment();
    }
}

public class Counter {
	private int state = 0;
    public void increment() { state++; }
    public int get() { return state; }
}

 

귤박스를 확인해보며 귤상태가 불량한다면 count를 하나씩 증가시키는 코드다.

여기서 badCounter는 두 스레드가 공유한다. 그래야만 전체 합쳐서 몇개의 귤이 불량인지 알 수 있다.

 

메모리 상태를 그려보자.

CPU Single Core, Thread 1 , Thraed 2 이 있다.

Memory [       [heap : badCounter, state : 0]     ]

Single Core이기에 Thread 2개이기에 멀티태스킹이 일어나기에, 번갈아가며 실행된다. 이  과정에서 Context Swithcing이 일어난다.(하지만 하나의 프로세스이기에 그나마 overhead가 적다.)

 

만약  Thread1에서 5개의 귤을 발견, Thread2 에서 2개의 귤을 발견햇다.

우리는 7 이라는 답을 찾야아한다.

 

increment 메서드를 보면 이 state ++ 는 실제로 CPU 레벨에서 어덯게 동작하는지 이해해야한다.

이 state ++ 가 CPU에서 동작방식은 다음과 같다.

프로그래밍 언어를 어셈블리어로 변환한다.

LOAD STATE TO R1 //메모리에 있는 state에 있는 변수라는 값을 R1이라는 CPU안에 포함된 레지스터를 가져온다.
R1 = R1 + 1 //Register에 값을 증가시켜준다.
STORE R1 to STATE //해당 R1 값을 메모리의 state에 올려준다.

 

Thread1과 Thread2가 타이밍에 따라서 어떻게 달라질 수 있는지 확인해보자.

 

Thread1에서 Memory의 state값을 R1 에 올린다. LOAD STATE TO R1

R1 = R1 + 1

 

문제는 이 타이밍에 Context Swithcing이 발생하여 Thraed2에서 작업을 시작한다.

 

Thread2에서 불량귤을 발견하고 다시 increment를 호출한다.

LOAD STATE TO R1 ( 아직 Memory의 state값은 0 인 상태이다. )

R1 = R1 + 1 이다.

STORE R1 to STATE 하여 이재 Memory의 state는 =1 이 되었다.

 

이 타이밍에 다시 Context Swithcing이 발생한다.

아까 못맞춘

STORE R1 to STATE 를 실행하면서

Memory 값이 여전히 1 이다. ( 2여야한다.)

 

increment함수가 2번 실행되어야하는데 Thread-Safe하지 않고, Context Swithcing으로 인해 Thread1이 작업하다가 메모리에 써지진 않은 상태에서 Thread2가 실행되었기 떄문이다.

 

하나의 Thread에서 언제 Context Swithcing이 일어나는 문제가 생긴다.

싱글코어가  아닌 듀얼 코어에서도 문제가 발생한다.

 

race condition(경쟁 조건)이란?

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

syncrhonization(동기화)이란?

여러 프로세스/스레드를 동시에 실행해도 이러한 RAce Condition 없이 공유데이터의 일관성을 유지하는것이 동기화(Syncrhonization)이라 한다.

어떻게 동기화 시킬 것인가?

for(귤 in 귤박스){
	if(귤 상태 is 불량){
    	badCounter.increment();
    }
}

public class Counter {
	private int state = 0;
    public void increment() { state++; }
    public int get() { return state; }
}

state++ 는 하나의 명령이 아닌 3줄의 명령이기에 문제가 발생한다.
LOAD STATE TO R1
R1 = R1 + 1
STORE R1 to state

처음에 드는 생각은, 이 state++, 즉 어셈블리어로 3가지 명령어를 실행하는 과정에서 COntext Swithcing이 일어나지 않도록 하면 좋겟다는 생각이 든다.

하지만, 이 방법은 멀티코어에서는 불가능하다. 이유는 멀티코어에서는 멀티태스킹이 아닌 Thread가 동시에 실행되기에 소용이 없다.

increment를 실행할때 한번에 이 메소드를 반드시 한 스레드만 실행할 수 있도록 하는것이다.

critical section(임계 영역)이란?

공유데이터의 일관성을 보장하기 위해 하나의 프로세스/스레드만 진입해서 실행가능한 영역을 임계영역, Critical Section 이라고 한다.

그리고 이 Critical Section 과 관련된 문제를 Critical Section Problem이라한다.

critical section 문제를 해결하는 뼈대

do {
	entry section
    	critical section //임계영역이다.
    exit section
    	remainder section
} while ( TRUE )

critical section 문제 해결책의 조건

1. mutual execlusion ( 상호 배제 ) : 한번에 하나의 프로세스/쓰레드만 하나의 Critical Section에서 작업한다.

2. progress( 진행 ) : 만약에 Critical Section 이 비어있고, 이 Critical Section에 들어가길 원한다면 그중 하나는 Critical Section에서 진행되도록 해야한다. 즉, 진행되도록 해야한다고 생각하라.

3. bounded waiting ( 한정된 대기 ) : 무한정 스레드가 Critical Section에 들어가기 위해 대기하고 있으면 안된다.

프로그래밍 할때 thead safe 한지 확인

SimpleDateFormat class 문서를 읽어보면 중간에 아래의 내용이 나온다.

Date formats are not syncrhonized. : 이 클래스는 동기화 되지 않았다.

It is recommended to create separate format instances for each thread. : 즉, 이것은 각각의 스레드에서 각각으로 사용해라.

If multiple threads access a format concurrently, it must be synchronized externally. : 만약 동시에 사용되려면 반드시 외부에서 동기화시켜야만 한다.

 

특히, 백엔드 API Server를 Multi Threading 환경에서 개발하는 상황이라면 더더욱 조심해야한다.

마무리

동기화가 왜 중요한지, 어떤 문제가 발생할 수 있는지 알아보았다.

다음에는 critical section probel solution에 대해 알아보자.

+ Recent posts