본문 바로가기
개인공부

Sychronization(동기화) / Synchronous(동기) / Asynchronous(비동기) 관련정보

by 리승우 2023. 3. 14.

그동안 막연하게 알고만 있었던 것들을 조금 더 자세히 공부해보았다.

위 개념에 대해 다루기 전에 관련 개념들부터 차근차근히 기재하고자 한다!

우선 Race Condition을 미리 알고 있어야 이해하기 편하다

 


Sychronization(동기화)


Race Condition (경쟁 상태)

여러 프로세스 / 쓰레드가 동시에 같은 데이터(공유자원)을 조작할 때, 타이밍이나 접근 순서에 따라 결과가 달라질 수 있는 상황

 

Synchronization (동기화)

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

 

Race Condition 예시코드 (싱글코어 기준, 동기화 실패현상)

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

여기서 쓰레드가 increase() 메서드를 실행시킨 뒤 state를 증가시킬 때,

CPU레벨에서는 아래와 같이 명령문이 실행된다.

 

LOAD state to R1   (state값을 R1에 Load)

R1 = R1 + 1            (R1 + 1을 진행)

STORE R1 to state (R1값을 state에 저장)

 

근데 여기서 만약 T1과 T2라는 쓰레드가 있을 때 아래와 같이 움직이면 Race Condition이 발생한다

1. T1쓰레드가 increase()메서드를 실행

2. CPU레벨 명령문 3행 중 2행까지만 실행되고 있는 중

3. T2쓰레드가 increase()메서드를 실행하여 Context Switching(쓰레드 교체) 발생

4. T2쓰레드는 T1쓰레드가 올바르게 증가시킨 state값을 이용하지 못하고 1을 반환하는 상황 발생

 

그럼 이제 해결 방법은 어떻게 될까?

바로 아래와 같다.

 

1개 쓰레드만 increase()를 실행할 수 있게 제한하여 해당 쓰레드가 메서드 호출을 끝낼 때까지 다른 쓰레드는 대기하게 처리하여 임계영역을 생성!!!

 

Critical Section (임계 영역) 이란?

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

 

나의 예시 TMI

이전에 이런 임계영역을 생성하기 위해 방법을 찾던 중, 쓰레드별로 Lock을 도입해야 겠다고 생각했다

마침 MySQL의 스토리지 엔진인 InnoDB에서는 Row-level locking Method를 지원해주고 있었다.

이는 수정 프로세스에서 테이블의 행만 잠그는 방식으로, 동일한 테이블에 대한 다중 접근을 가능하게 해주는 기능이였다.

 

그리고 Row-level locking Method 에서 대표적인 종류는 

Shared and Exclusive lock (공유와 배타적인 잠금) 이 있었고

그래서 Row-level locking Method에서 제공하는 기능인 Pessimistic Lock을 활용하여 Lock을 진행함으로써 

임계영역을 생성하게 되었다.

 

사용하는 방법은 아래와 같으며, 나는 쓰기 작업이 있는 트랜잭션이여서 WRITE를 사용하였다.

@Transactional
@Lock(value = LockModeType.PESSIMISTIC_WRITE) //여기
public int decreasePrice(String name, int price)
  • LockModeType.PESSIMISTIC_WRITE
    일반적인 옵션. 데이터베이스에 쓰기 락
    다른 트랜잭션에서 읽기도 쓰기도 못함. (배타적 잠금)
  • LockModeType.PESSIMISTIC_READ
    반복 읽기만하고 수정하지 않는 용도로 락을 걸 때 사용
    다른 트랜잭션에서 읽기는 가능함. (공유 잠금)
  • LockModeType.PESSINISTIC_FORCE_INCREMENT
    Version 정보를 사용하는 비관적 락

 


Synchronous(동기) / Asynchronous(비동기)


자! 이번에 위 개념에 알아보기에 앞서

block I/O와 non-block I/O에 대해서도 아주아주아주 간단히만 알아보겠다

 

block I/O

I/O 작업을 요청한 프로세스 / 쓰레드는 요청이 완료될 때까지 블락되는 것

EX) 나 이거 하고 있어서 아무것도 못해요~

non-block I/O

프로세스 / 쓰레드를 블락시키지 않고 요청에 대한 현재 상태를 즉시 리턴

* 블락되지 않고 즉시 리턴하기 때문에 쓰레드가 다른 작업을 수행할 수 있다

EX) 이거 우선 시켜두었고 그동안 나는 다른 것도 할게요~

 

이제 해당 개념들을 토대로 

Synchronous(동기) 와 Asynchronous(비동기)를 간단히 알아보겠다!

 

Synchronous(동기)

=> 쓰레드가 작업을 요청할 시, 요청한 자리에서 결과가 주어져야 하는 것

(쓰레드가 작업을 요청할 시, 응답 결과가 오기 전까지 쓰레드는 대기함)

 

위 개념을 간단히 숙지하면 Synchronous Programming의 개념은 아래와 같다

=> 여러작업 (Task) 들을 순차적으로 실행하도록 개발하는 방법론

 

그에 대한 예시로 아래 그림 내용이 있다

1. 한 개의 쓰레드가 block I/O 기반 순차적으로 실행

 

Asynchronous(비동기)

=> 쓰레드가 작업을 요청한 후, 그 결과가 요청한 자리에서 오지 않아도 되는 것

(쓰레드가 작업을 요청할 시, 응답 결과가 오지 않더라도 다른 작업을 할 수 있음)

 

위 개념을 간단히 숙지하면 Asynchronous Programming의 개념은 아래와 같다

=> 여러 작업들을 독립적으로 실행하도록 개발하는 방법론

 

이에 유사한 개념인 multithreading은 아래와 같다

=> Asynchronous Programming의 한 종류

 

최종적으로, Asynchronous Programming을 가능하게 하는 것은 아래 2개가 있다

1. multi-threads (왼쪽)

=> 여러가지 작업들을 multi-threads가 나눠 가져서 동시에 실행

 

장점 >> 멀티 코어를 활용할 수 있다

 

단점

1. 쓰레드가 많아질 경우 Context Switching 비용을 많이 쓰게됨

2. Race Condition 발생 가능성이 높아, 유의하며 개발해야 함

 

2. non-block I/O (오른쪽)

=> 하나의 싱글 쓰레드가 I/O 작업을 하는데, Blocking하지않고 다른 I/O 작업도 같이 진행

 

자세한 예시로 아래 그림 내용이 있다

1. 한 개의 쓰레드가 non-block I/O 기반으로 여러 개의 작업을 독립적으로 동시에 실행

2. Multi-threads를 이용하여 적은 쓰레드로 전체 처리량을 증가

 

위 2개의 개념을 잘 버무리면 결국 아래 그림과 같이 자원을 보다 효율적으로 쓸 수 있게된다.

적은 쓰레드로도 좋은 성능(전체 처리량이 좋은)을 낼 수 있는 프로그래밍을 할 수 있게 되는 것이다!

 

 

정말 어려운 개념이다. 

이제 이걸 어떻게 해야하는가?

에 대해서 배우고, 직접 실행해보며 알아가야 할 것 같다.

 

공부는 늘 끝이 없다는 걸 느낀다.

댓글